Django model CreateView does not render form fields in HTML - python

I am trying to setup a class based 'CreateView' for a model in my django site, but when the create html page renders, the model fields are not rendered. Only the submit button shows up on the web page. However, when debugging, I overrided the 'form_invalid' method in the class view, and the form object had the required HTML for all fields stored in the object. If I take this HTML and manually add it to the HTML of the create page in the browser I can fill out the fields and post the data to the database.
At this point I have not found an obvious answer as to why the form fields are not rendered so any help on this would be greatly appreciated.
environment used: python 3.7.3, django 2.2.3
Solution:
This issue was fixed by changing the form name in the view context data.
In views.py:
def get_context_data(self, *args, **kwargs):
context = super(CreateAlertView, self).get_context_data(**kwargs)
context["alert_form"]=context["form"]
return context
Or...
In the HTML template change 'alert_form' to 'form' to match the default context.
models.py:
class Alert(models.Model):
RAIN = 'Rain'
SNOW = 'Snow'
COLD = 'Cold'
HEAT = 'Heat'
WEATHER_CHOICES = [
(RAIN, 'Rain'),
(SNOW, 'Snow'),
(COLD, 'Cold'),
(HEAT, 'Heat'),
]
DAILY = 'Daily'
WEEKLY = 'Weekly'
INTERVAL_CHOICES = [
(DAILY, 'Daily'),
(WEEKLY, 'Weekly'),
]
weather_type = models.CharField(max_length=15, choices=WEATHER_CHOICES, default=RAIN)
interval = models.CharField(max_length=10, choices=INTERVAL_CHOICES, default=DAILY)
search_length = models.IntegerField(default=1)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
active = models.BooleanField(default=False)
views.py:
class CreateAlertView(LoginRequiredMixin, CreateView):
template_name = 'users/alert_form.html'
#model = Alert
form_class = AlertModelForm
success_url = 'users/profile/'
def form_valid(self, form):
print('validation')
form.instance.user = self.request.user
return super().form_valid(form)
def form_invalid(self, form):
print(form) # check form HTML here
return super().form_invalid(form)
forms.py:
class AlertModelForm(ModelForm):
class Meta:
model = Alert
exclude = ['user']
urls.py:
urlpatterns = [
path('alert/create/', CreateAlertView.as_view(), name='alert'),
]
html template:
<h1>create an alert</h1>
<form method="post">
{% csrf_token %}
{{ alert_form.as_p }}
{{ alert_form.non_field_errors }}
{{ field.errors }}
<button type="submit">Save changes</button>
</form>
Create page as rendered:
Create page with manually modified HTML:

The context name for the form set by the CreateView (FormMixin) is "form", your template is referencing "alert_form"
Here is a helpful website for seeing all options available in the class based views

Related

Which one is more convenient to create django form, CreateView or forms.ModelForm

I am very beginner in django. I want to create a post form which be able to have title, content, image/file upload and etc.
I am very confused with concept of modelforms and createview. I tried this:
blog/view.py:
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'content', 'imagefile']
success_url = '/blog/home/'
# template_name = 'blog/post_form.html'
def __init__(self, *args, **kwargs):
super(PostCreateView, self).__init__(**kwargs) # Call to ModelForm constructor
def form_valid(self, form):
form.instance.author = self.request.user
form.save()
return super().form_valid(form)
blog/templates/blog/post_form.html:
{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="content-section">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Post::</legend>
{{ form|crispy }}
<img src='{{ post.imagefile.url }}'>
<br><br>
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Update</button>
</div>
</form>
</div>
{% endblock content %}
blog/urls.py:
from django.urls import path
from .views import (
PostCreateView,
)
urlpatterns = [
path('blog/post/new/', PostCreateView.as_view(), name='post-create')
]
blog/models.py
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
# image = models.ImageField(default='default_post.jpg', upload_to='postimages')
imagefile = models.FileField(upload_to='postimages', null=True, verbose_name="")
# if user is deleted the idea should be deleted as
author = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
# return self.title
return self.title + ": " + str(self.imagefile)
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.pk})
My question: All I want is make a kind of form to create the new post with title, content, upload button and submit button. However I don't know if CreateView can be customised even for adding further items or forms.Modelforms should be used?
You would want your view to be in views.py and your form to be in forms.py. You will need both, not one or the other.
Something like:
class CreateXYZView(CreateView):
template_name = "blog/post_form.html"
model = Post
form_class = postform
...do view stuff
def post(self, request, *args, **kwargs):
Also have a forms.py file
class postform(forms.ModelForm):
class Meta:
model = Post
widgets = {}
exclude = ['fieldname']
It looks like you are using a model named Post which is a good idea. By declaring postform as a forms.ModelForm it will pull your model fields into the form I.E from class Post it pulls title, content, imagefield, etc.. unless the field is specifically added to the exclude parameter. This is useful for parameters in your model like auto fields created_by or post_date where the user should not fill these in manually. Add these to exclude so they do not show in the form.
You can also manually add form fields in your template after {{form|crispy}} but I would avoid that as it creates more work in processing the data.
If you are filling out a form that is not tied to a model you can also use forms.Form:
class SupportTicket(forms.Form):
title = forms.CharField(label="Titlte", max_length=250, widget=forms.TextInput(...)
content = forms....
E.G. use this where the information was being passed directly to GitLab and not saved locally into a model for use later.

Django UpdateView: cannot get form fields to show database values

I found multiple answers to this same questions but unfortunately, I can't seem to figure it out :(
The form has a drop-down list for the 'subcategory' field in my model 'PhysicalPart', the values of the 'subcategory' field are updated dynamically upon the form creation (using a 'category' parameter).
Unfortunately, I can't get the drop-down to show all subcategories AND have the one from the database selected at the same time. I can't seem to retrieve the 'short_description' value either from the database.
It used to work before I learned about UpdateView class and decided to use it instead...
Any insight on how-to workaround my problem would be appreciated!
forms.py
class PartForm(forms.ModelForm):
subcategory = forms.ChoiceField(choices=[])
class Meta:
model = PhysicalPart
fields = ['subcategory', 'short_description']
views.py
class PartUpdate(UpdateView):
model = PhysicalPart
template_name = 'part_update.html'
form_class = PartForm
def post(self, request, *args, **kwargs):
# Load model instance
self.object = self.get_object()
# Load form
form = super(PartUpdate, self).get_form(self.form_class)
# Populating subcategory choices
form.fields['subcategory'].choices = SubcategoryFilter[self.object.category]
# Check if form valid and save data
if form.is_valid():
form.save()
return redirect('part-list')
# Update context before rendering
context = self.get_context_data(**kwargs)
context['part_id'] = self.object.pk
context['part_category'] = self.object.category
context['manufacturing_list'] = self.object.manufacturing.all()
return render(request, self.template_name, context)
html
<form action="{% url 'part-update' pk=part_id category=part_category %}" method="post" style="display: inline">
{% csrf_token %}
<div class="form">
<p class="font-weight-bold">Type</br>
{{ form.subcategory }}
</p>
</div>
<div class="form">
<p class="font-weight-bold">Short Description</br>
{{ form.short_description }}
</p>
</div>
<button type="submit" class="btn btn-primary">Save</button>
</form>
<form action="{% url 'part-list' %}" style="display: inline">
<button type="submit" class="btn btn-danger">Cancel</button>
</form>
My problem was that I did not differentiate the "GET" versus the "POST" calls in the UpdateView class, I was trying to do everything in the post() method. It took me a while to figure it out but now I think it's clear.
I originally used the get() method but I realize that get_context_data() was better suited as it automatically loads most of the context (eg. the instance and the form), instead of having to do everything from scratch in the get() method.
Scrubbing through the code of the UpdateView class here, it also seemed necessary to add ModelFormMixin into the declaration of the PartUpdate class so that the get_context_data() method automatically loads the form associated to the target model/instance (else it looks like it won't do it).
Here is my updated views.py code:
class PartUpdate(UpdateView, ModelFormMixin):
model = PhysicalPart
template_name = 'part_update.html'
form_class = PartForm
success_url = reverse_lazy('part-list')
def get_context_data(self, **kwargs):
# Load context from GET request
context = super(PartUpdate, self).get_context_data(**kwargs)
# Get id from PhysicalPart instance
context['part_id'] = self.object.id
# Get category from PhysicalPart instance
context['part_category'] = self.object.category
# Add choices to form 'subcategory' field
context['form'].fields['subcategory'].choices = SubcategoryFilter[self.object.category]
# Return context to be used in form view
return context
def post(self, request, *args, **kwargs):
# Get instance of PhysicalPart
self.object = self.get_object()
# Load form
form = self.get_form()
# Add choices to form 'subcategory' field
form.fields['subcategory'].choices = SubcategoryFilter[self.object.category]
# Check if form is valid and save PhysicalPart instance
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
From my understanding you are trying to edit an instance. This is how you do it in Django, it should autopopulate your inputs with the proper values :
my_record = MyModel.objects.get(id=XXX)
form = MyModelForm(instance=my_record)
More details on this answer : how to edit model data using django forms
If your models are properly done (with relations) you shouldn't need to provide the choices for the Select.

List of current user objects in Django ListView

I want to render list of all objects on my template, for which their author is the currently logged in user. I passed the username of current user to url.py:
My List
My urls.py:
path('myscenarios/<str:username>/', MyScenarioListView.as_view(), name='myscenarios'),
My question is how to build the queryset in views.py and what to type in template block in my html?
class MyScenarioListView(LoginRequiredMixin, ListView):
model = Scenario
template_name = 'testmanager/myscenarios.html'
context_object_name = 'myscenarios'
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Scenario.objects.filter(scenarioAuthor = user).order_by('-date_posted')
What code should I type in my myscenarios.html file?
I want to render list of all objects on my template, which their author is current logged user.
Then you should not encode the user in the path, since a "hacker" can then simply change the URL to see the items belonging to a different user.
You can make use of self.request.user here. The path thus looks like:
path('myscenarios/', MyScenarioListView.as_view(), name='myscenarios'),
and in the view, we use:
class MyScenarioListView(LoginRequiredMixin, ListView):
model = Scenario
template_name = 'testmanager/myscenarios.html'
context_object_name = 'myscenarios'
def get_queryset(self):
return Scenario.objects.filter(
scenarioAuthor=self.request.user
).order_by('-date_posted')
It will pass the Scenarios as myscenarios to the template, so you can render this with:
{% for scenario in myscenarios %}
{{ scenario }}
{% endfor %}

Using Django's 'get_absolute_url' (return the user to the same page after form submission)

I am learning Django and have one question.
I have done a feedback form and I need to redirect the user to the same page after the feedback form confirmation. Below is the code:
models.py
class Feedback(models.Model):
title = models.CharField(max_length=255)
text = models.TextField(max_length=5000)
user_name = models.CharField(max_length=255)
user_lastname = models.CharField(max_length=255)
email = models.EmailField(max_length=255)
send_time = models.DateTimeField(auto_now_add=True)
update_time = models.DateTimeField(auto_now=True)
def get_absolute_url(self):
return
urls.py
url(r'^feedback$',views.FeedbackSendForm.as_view(), name='feedback'),
views.py
class FeedbackSendForm(CreateView):
model = Feedback
fields = [
'title',
'text',
'user_name',
'user_lastname',
'email',
]
template_name = 'feedback.html'
feedback.html
<form method="post">
{% csrf_token %}
{% for field in form %}
<span class="text-danger">{{ field.errors }}</span>
<div>
<label class="control-label">{{ field.label }}</label>
{{ field }}
</div>
{% endfor %}
<button type="submit">Submit</button>
</form>
How can I fix this?
If you want to override the get_absolute_url for your model, the following code can help you:
from django.urls import reverse
class Feedback(models.Model):
# The model fields
def get_absolute_url(self):
# reverse expects the view name
return reverse('feedback')
The absolute URL for any Feedback object will be the view FeedbackSendForm. That is specified by passing the view name feedback to reverse.
Example Createview:
views.py:
class FeedbackSendForm(CreateView):
model = Feedback
fields = ['title','text','user_name','user_lastname','email',]
template_name = 'feedback.html'
form_class = form_name
def form_valid(self, form):
"""
If the form is valid, redirect to the supplied URL.
"""
return HttpResponseRedirect(self.get_success_url())
"""
define `get_success_url' to your model or use `get_absolute_url` instead.
More information: class CreateView
If we define get_absolute_url in the model class then, while posting the form, we can leave the action tag as empty like this:
<form action="" method="post">
In this case, it now searches for the get_absolute_url in our model class defined as below:
def get_absolute_url(self):
return reverse("post_detail", kwargs={"pk": self.pk})
After updating or adding data in the model, our page is redirected to the URL named as post_detail.

How to search on a Many to Many field in Django?

I have a Profile model with a ManyToManyField on another model Specialty.
I want to have a simple search on the Profile model against specialties and return matching profiles. As it stands, my form displays in my template correctly, but I can't get anything after the submission.
models.py:
from django.db import models
from django.conf import settings
class Specialty(models.Model):
title = models.CharField(max_length=255)
class Meta:
verbose_name_plural = 'Specialties'
def __unicode__(self):
return u"%s" % self.title
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
specialties = models.ManyToManyField(Specialty, blank=True)
def __unicode__(self):
return u"%s" % (self.user.username)
def get_absolute_url(self):
return reverse("profile_detail", args=[str(self.user.username)])
forms.py:
from django import forms
from .profiles.models import Profile, Specialty
class ProfileSearchForm(forms.ModelForm):
specialty = forms.ModelMultipleChoiceField(queryset=Specialty.objects.all(), widget=forms.CheckboxSelectMultiple, required=False)
class Meta:
model = Profile
fields = ('specialty',)
views.py:
from django.views.generic.edit import FormView
from django.core.urlresolvers import reverse_lazy
from .forms import ProfileSearchForm
from .profiles.models import Profile
class IndexView(FormView):
template_name = 'index.html'
form_class = ProfileSearchForm
success_url = reverse_lazy('index')
def form_valid(self, form):
specialty = form.cleaned_data['specialty']
self.profile_list = Profile.objects.filter(specialty__in=specialty)
return super(IndexView, self).form_valid(form)
index.html:
<form action="{% url 'index' %}" method="get">
{{ form.as_p }}
<p><input type="submit" value="Search"></p>
</form>
<ul>
{% for profile in profile_list %}
<li>{{ profile.user.get_full_name }}</li>
{% endfor %}
</ul>
I have a feeling it has to do with self.profile_list. I don't know if/how it should go into a get_extra_context. It can't exist on the first visit, so I don't know how to make it exist or pass it around. I'm also not sure if the Profile.objects.filter(specialty__in=specialty) is quite the right way to field lookup on a many-to-many field.
I'm also open to other search suggestions like Haystack if they have advantages. I prefer a group of checkboxes, which I don't think Haystack can handle via faceting.
Thanks, Gergo and Cameron. I got it fixed now. You were right about that one problem, but there were quite a few steps left to go.
What I really wanted was a ListView plus the ability to do a simple search, which should be a FormMixin that lets me add form_class and success_url, instead of it all as a FormView.
When a default model is specified in a ListView, the view blows away the context, so form never reaches the template. get_context_data needs to add the form back to the context, of which the docs have an example.
form_valid should be removed because a search is never a POST request, despite what the docs say under the "Note" in FormMixin requiring form_valid and form_invalid.
I need get_queryset to either get a default queryset via model or read the GET request's specialties value and filter the results appropriately.
For bonus points, get_form_kwargs needs to pass the current request to the form so initial form values can remain after a page refresh. The tricky part is that when using ModelMultipleChoiceField, you have to use request.GET's getlist and not get method to read that list of values.
All together now...
forms.py:
from django import forms
from .profiles.models import Profile, Specialty
class ProfileSearchForm(forms.ModelForm):
specialties = forms.ModelMultipleChoiceField(queryset=Specialty.objects.all(), widget=forms.CheckboxSelectMultiple, required=False)
class Meta:
model = Profile
fields = ('specialties',)
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request')
super(ProfileSearchForm, self).__init__(*args, **kwargs)
self.fields['specialties'].initial = self.request.GET.getlist('specialties')
views.py:
from django.views.generic import ListView
from django.views.generic.edit import FormMixin
from django.core.urlresolvers import reverse_lazy
from .profiles.models import Profile
from .forms import ProfileSearchForm
class IndexView(FormMixin, ListView):
model = Profile
template_name = 'index.html'
form_class = ProfileSearchForm
success_url = reverse_lazy('index')
def get_queryset(self):
queryset = super(IndexView, self).get_queryset()
specialties = self.request.GET.getlist('specialties')
if specialties:
queryset = queryset.filter(specialties__in=specialties).distinct('user')
return queryset
def get_form_kwargs(self):
kwargs = super(IndexView, self).get_form_kwargs()
kwargs['request'] = self.request
return kwargs
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
form_class = self.get_form_class()
context['form'] = self.get_form(form_class)
return context
I think you're looking for Profile.objects.filter(specialties__in=specialty) - profile doesn't have a specialty field, it has a specialties field.

Categories

Resources