I'm using Django django.forms.Form and django.views.generic.edit.FormView to render a HTML template.
I would to add a default value for some of the fields in my form but it encounters a problem:
Here is my code:
from django.views.generic.base import TemplateView
from django.views.generic.edit import FormView
class SignForm(forms.Form):
greeting_message = forms.CharField(
label='Greeting message',
widget=forms.Textarea,
required=True,
max_length=100,
)
book_name = forms.CharField(
label='Guestbook name',
max_length=10,
required=True,
)
class SignView(FormView):
"""Assign initial value for field 'book_name'."""
form_class = SignForm(
initial={
'book_name': 'aaaa'
}
)
def form_valid(self, form):
...
Can anyone help me?
Like the comment above says, you must assign form_class just to the class. By doing what you have with the parenthesis and arguments, you are instantiating an object, which is why you have an exception.
Instead, to set initial data, define the get_initial function, like so:
def get_initial(self):
initial = super(SignView, self).get_initial()
initial['book_name'] = 'aaaa'
return initial
Docs are available here.
You are attributing an instance to form_class instead of a form class like the name of the attribute implies. SignForm is the class, SignForm(*args) is an instance.
You should override the get_initial() method in the view:
def get_initial():
return {'book_name': 'aaaa'}
Related
I'm trying to create a view of type CreateView. This view will take the form_class = CourseForm that I created and excluded some fields in it. The instructor field is a foriegnKey and I don't want the user to be able to control it in the form. It's a field that depends on the signed in user.
# forms.py
class CourseForm(forms.ModelForm):
class Meta:
model = Course
exclude = ['instructor', 'members', 'slug']
# ...
my view is as follows. I thought that by including the instructor value in initial would pass the profile instance when I submit
# views.py
#method_decorator(login_required, name='dispatch')
class CourseCreateView(CreateView):
model = Course
template_name = 'course_form.html'
success_url = reverse_lazy('course-create-complete')
form_class = CourseForm
def get_initial(self):
initial = super(CourseCreateView, self).get_initial()
initial = initial.copy()
profile = get_object_or_404(Profile, user__username=self.request.user)
initial['instructor'] = profile
return initial
# models.py
class Course(models.Model):
instructor = models.ForeignKey(Profile, related_name="Instructor")
# ... other fields
but the probelm is that whenever I submit the form I get the following error:
NOT NULL constraint failed: myapp_course.instructor_id
If you want to set the initial value of instructor field, you shouldn't exclude it from the form. You could instead make the field hidden.
Or you could include that in the exclude list, but then you shouldn't override get_initial method, but do the assignment manually:
class CourseCreateView(CreateView):
def form_valid(self, form):
self.object = form.save(commit=False)
# create instructor based on self.request.user
self.object.instructor = instructor
self.object.save()
return HttpResponseRedirect(self.get_success_url())
Check django doc about what does save(commit=False) do.
Also check django doc about form_valid function and how forms are handled in class based views.
I have a model with good validation, I'm using the clean method inside the model. the problem is when I am validating I am using an object that has not been set in the form which raise an exception that the object is not there yet.
I want a solution to pass the object from url primary key to the form before any validation, so my clean method works fine.
Here is a similar example.
The main model
class Student(models.Model):
first_name = models.CharField(max_length=30)
lets sat that each student might have one semester at a time. However, if there are any semesters before then the start date must be after the last semester end date.
class Semester(models.Model):
student = models.OneToOneField(Student)
start_date = models.DateField()
def clean(self):
# do not allow the start date to be before last semester end date
if self.student.semesterhistory_set.all().count() > 0:
last_semester_end_date = self.student.semesterhistory_set.last().end_date
if last_semester_end_date >= self.start_date:
message = _("Start Date for this semester must be after %s" % last_date)
raise ValidationError(message)
class SemesterHistory(models.Model):
student = models.ForeignKey(Student)
start_date = models.DateField()
end_date = models.DateField()
In the view, I am passing the student object which will be used in validation after validating the form. (problem)
# URL for this is like this student/(pk)/semesters/create/
class SemesterCreate(CreateView):
model = Semester
fields = ['start_date']
def form_valid(self, form):
form.instance.student = get_object_or_404(Student, id=int(self.kwargs['pk']))
return super(SemesterCreate, self).form_valid(form)
Error:
RelatedObjectDoesNotExist Semester has no student
Obviously you need call form.save(commit=False) which returns instance ... Also semantically wrong approach raise 404 in form_valid...
class SemesterCreate(CreateView):
model = Semester
fields = ['start_date']
student = object = None
def dispatch(self, request, *args, **kwargs):
self.student = get_object_or_404(Student, id=kwargs['pk'])
return super(SemesterCreate, self).dispatch(request, *args, **kwargs)
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.student = self.student
self.object.save()
return HttpResponseRedirect(self.get_success_url())
def get_success_url(self):
return reverse('...')
https://docs.djangoproject.com/en/1.8/topics/forms/modelforms/#the-save-method
Actually to have a clean set I would add a custom ModelForm. I also use CreateView and this is how i use it .
First add a custom ModelForm (I personnaly add a forms.py file in my apps):
from django.contrib.auth.forms import UserCreationForm
from .model import Semester
class CreateSemesterForm(UserCreationForm):
error_messages = {
'last_date': _("some message"),
}
class Meta:
model = Semester
fields = ('some', 'fields') #or __all__
def clean_your_date_field(self):
#clean_name_field will be called when the form_valid will be called with all clean_fields functions
#here you define your clean method and raise Validation error
return field
def save(self, commit=True):
semester = super(CreateSemesterForm, self).save(commit=False)
#here you can set some other values
if commit:
semester.save()
return semester
And in your custom CreateView you have to add :
class SemesterCreate(CreateView):
form_class = CreateArtistForm
As you set model and fields in the ModelForm you can remove fields and model args from CreateView.
You also can override form_valid in your Custom ModelForm.
Now CreateView will call form_valid which call all clean functions, and if it's all passes, it returns and save your semester.
I came across this yesterday after facing the exact same issue with my project.
It's been a couple of years since you've posted this, but figure I'd post my solution to help anyone else out who might stumble across this.
The solution I came across is to use a custom modelform:
from django import forms
from .models import Blade
class SemesterForm(forms.ModelForm):
class Meta:
model = Semester
fields = '__all__'
widgets = {'student': forms.HiddenInput()}
And the in your view:
class SemesterCreate(CreateView):
model = Semester
def get_initial(self, **kwargs):
# get current student from pk in url
current_student = get_object_or_404(Student,
pk=self.kwargs.get('pk'))
return { 'site': current_student }
The trick here is that you must set the student field to hidden in the form. That way, it will keep the initialised value you give it in the view, but won't be available to the user.
So, it will be there when the form is submitted and the full_clean() method is called (which will then call the clean() method on the model), and your nice and tidy validations performed in the model clean() will work.
I still didn't understood the UpdateView. Where does it fetch the form from?
It has exactly the fields declared in the model, but doesn't use the form defined in forms.py.
I did however follow the answer given at:
How does one use a custom widget with a generic UpdateView without having to redefine the entire form?
In my case I use an IntegerField in model.py and use Radiobuttons in the Form.
So what the UpdateView does is giving me an IntegerField instead of a ChoiceField. Even when I assigned the RadioSelect Widget or a Choice Field:
The View:
class UpdateEinflussideen(UpdateView):
model = Einflussideen
EINFLUSS = [(10,'hoch'),(4,'mittel'),(1,'gering')]
form_class = forms.models.modelform_factory(Einflussideen,
widgets={'einfluss': forms.ChoiceField(
choices=EINFLUSS, widget=forms.RadioSelect())},
)
template_name = 'verbrauchererfassung/update_einflussideen.html'
success_url = reverse_lazy('verbraucher')
The Model:
class Einflussideen(models.Model):
idee = models.CharField(max_length=100)
einfluss = models.IntegerField()
verbraucher = models.ForeignKey(Verbraucher)
Variables in python are case-sensitive. Change the atribute form_Class to the form_class. Also the widgets argument should contain a dict with the Widget instances in the values:
form_class = forms.models.modelform_factory(Einflussideen,
widgets={'einfluss': forms.RadioSelect(choices=EINFLUSS)})
I have a custom class-based view
# myapp/views.py
from django.views.generic import *
class MyView(DetailView):
template_name = 'detail.html'
model = MyModel
def get_object(self, queryset=None):
return queryset.get(slug=self.slug)
I want to pass in the slug parameter (or other parameters to the view) like this
MyView.as_view(slug='hello_world')
Do I need to override any methods to be able to do this?
If your urlconf looks something like this:
url(r'^(?P<slug>[a-zA-Z0-9-]+)/$', MyView.as_view(), name = 'my_named_view')
then the slug will be available inside your view functions (such as 'get_queryset') like this:
self.kwargs['slug']
Every parameter that's passed to the as_view method is an instance variable of the View class. That means to add slug as a parameter you have to create it as an instance variable in your sub-class:
# myapp/views.py
from django.views.generic import DetailView
class MyView(DetailView):
template_name = 'detail.html'
model = MyModel
# additional parameters
slug = None
def get_object(self, queryset=None):
return queryset.get(slug=self.slug)
That should make MyView.as_view(slug='hello_world') work.
If you're passing the variables through keywords, use what Mr Erikkson suggested: https://stackoverflow.com/a/11494666/9903
It's worth noting you don't need to override get_object() in order to look up an object based on a slug passed as a keyword arg - you can use the attributes of a SingleObjectMixin https://docs.djangoproject.com/en/1.5/ref/class-based-views/mixins-single-object/#singleobjectmixin
# views.py
class MyView(DetailView):
model = MyModel
slug_field = 'slug_field_name'
slug_url_kwarg = 'model_slug'
context_object_name = 'my_model'
# urls.py
url(r'^(?P<model_slug>[\w-]+)/$', MyView.as_view(), name = 'my_named_view')
# mymodel_detail.html
{{ my_model.slug_field_name }}
(both slug_field and slug_url_kwarg default to 'slug')
If you want to add an object to the context for the template you can override get_context_data and add to its context. The request is also a part of self in case you need the request.user.
def get_context_data(self, **kwargs):
context = super(MyTemplateView, self).get_context_data(**kwargs)
if 'slug' in self.kwargs:
context['object'] = get_object_or_404(MyObject, slug=self.kwargs['slug'])
context['objects'] = get_objects_by_user(self.request.user)
return context
You can pass parameters from urls.py
https://docs.djangoproject.com/en/1.7/topics/http/urls/#passing-extra-options-to-view-functions
This also works for generic views. Example:
url(r'^$', views.SectionView.as_view(), { 'pk': 'homepage', 'another_param':'?'}, name='main_page'),
In this case the parameters passed to the view should not necessarily be instance variables of the View class. Using this method you don't need to hardcode default page name into YourView model, but you can just pass it as a parameter from urlconf.
As stated by Yaroslav Nikitenko, if you don't want to hardcode a new instance variable to the View class, you can pass extra options to view functions from urls.py like this:
url(r'^$', YourView.as_view(), {'slug': 'hello_world'}, name='page_name')
I just wanted to add how to use it from the view. You can implement one of the following methods:
# If slug is optional
def the_function(self, request, slug=None):
# use slug here
# if slug is an optional param among others
def the_function(self, request, **kwargs):
slug = kwargs.get("slug", None)
other_param = kwargs.get("other_param", None)
# If slug is required
def the_function(self, request, slug):
# use slug here
For django 3.0, this is what worked for me:
# myapp/views.py
from django.views.generic import DetailView
class MyView(DetailView):
template_name = 'detail.html'
slug = None
def get_object(self, queryset=None):
self.slug = self.kwargs.get('slug', None)
return queryset.get(slug=self.slug)
# myapp/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('slug/<slug:slug>/', views.MyView.as_view(), name='myview_by_tag'),
]
A Django autofield when displayed using a formset is hidden by default. What would be the best way to show it?
At the moment, the model is declared as,
class MyModel:
locid = models.AutoField(primary_key=True)
...
When this is rendered using Django formsets,
class MyModelForm(ModelForm):
class Meta:
model = MyModel
fields = ('locid', 'name')
it shows up on the page as,
<input id="id_form-0-locid" type="hidden" value="707" name="form-0-locid"/>
Thanks.
Edit
I create the formset like this -
LocFormSet = modelformset_factory(MyModel)
pformset = LocFormSet(request.POST, request.FILES, queryset=MyModel.objects.order_by('name'))
Second Edit
Looks like I'm not using the custom form class I defined there, so the question needs slight modification..
How would I create a formset from a custom form (which will show a hidden field), as well as use a custom queryset?
At the moment, I can either inherit from a BaseModelFormSet class and use a custom query set, or I can use the ModelForm class to add a custom field to a form. Is there a way to do both with a formset?
Third Edit
I'm now using,
class MyModelForm(ModelForm):
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
locid = forms.IntegerField(min_value = 1, required=True)
self.fields['locid'].widget.attrs["type"] = 'visible'
self.queryset = MyModel.objects.order_by('name')
class Meta:
model = MyModel
fields = ('locid', 'name')
LocFormSet = modelformset_factory(MyModel, form = MyModelForm)
pformset = LocFormSet()
But this still doesn't
Show locid
Use the custom query that was specified.
Try changing the default field type:
from django import forms
class MyModelForm(ModelForm):
locid = forms.IntegerField(min_value=1, required=True)
class Meta:
model = MyModel
fields = ('locid', 'name')
EDIT: Tested and works...
As you say, you are not using the custom form you have defined. This is because you aren't passing it in anywhere, so Django can't know about it.
The solution is simple - just pass the custom form class into modelformset_factory:
LocFormSet = modelformset_factory(MyModel, form=MyModelForm)
Edit in response to update 3:
Firstly, you have the redefinition for locid in the wrong place - it needs to be at the class level, not inside the __init__.
Secondly, putting the queryset inside the form won't do anything at all - forms don't know about querysets. You should go back to what you were doing before, passing it in as a parameter when you instantiate the formset. (Alternatively, you could define a custom formset, but that seems like overkill.)
class MyModelForm(ModelForm):
locid = forms.IntegerField(min_value=1, required=True)
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
self.fields['locid'].widget.attrs["type"] = 'visible'
class Meta:
model = MyModel
fields = ('locid', 'name')
LocFormSet = modelformset_factory(MyModel, form = MyModelForm)
pformset = LocFormSet(request.POST, request.FILES,
queryset=MyModel.objects.order_by('name')))
Okay, none of the approaches above worked for me. I solved this issue from the template side, finally.
There is a ticket filed (http://code.djangoproject.com/ticket/10427), which adds a "value" option to a template variable for a form. For instance, it allows,
{{form.locid.value}}
to be shown. This is available as a patch, which can be installed in the SVN version of django using "patch -p0 file.patch"
Remember, the {{form.locid.value}} variable will be used in conjunction with the invisible form - otherwise, the submit and save operations for the formset will crash.
This is Not the same as {{form.locid.data}} - as is explained in the ticket referred to above.
The reason that the autofield is hidden, is that both BaseModelFormSet and BaseInlineFormSet override that field in add_field. The way to fix it is to create your own formset and override add_field without calling super. Also you don't have to explicitly define the primary key.
you have to pass the formset to modelformset_factory:
LocFormSet = modelformset_factory(MyModel,
formset=VisiblePrimaryKeyFormSet)
This is in the formset class:
from django.forms.models import BaseInlineFormSet, BaseModelFormSet, IntegerField
from django.forms.formsets import BaseFormSet
class VisiblePrimaryKeyFormset(BaseModelFormSet):
def add_fields(self, form, index):
self._pk_field = pk = self.model._meta.pk
if form.is_bound:
pk_value = form.instance.pk
else:
try:
pk_value = self.get_queryset()[index].pk
except IndexError:
pk_value = None
form.fields[self._pk_field.name] = IntegerField( initial=pk_value,
required=True) #or any other field you would like to display the pk in
BaseFormSet.add_fields(self, form, index) # call baseformset which does not modify your primary key field