I succesfully implemented the django-selectable AutoCompleteSelectField in a simple form that lets you enter an note description and a corresponding domain category ( domain and foreign key picked from other Many-to-One relationship
See: most relevant code:
# MODEL
class Note(models.Model):
notetext = models.TextField(default='nota')
domain = models.ForeignKey(Domain)
def __str__(self):
return self.notetext
def get_absolute_url(self):
return reverse('note:note_detail', args= [self.id])
# FORM
class NoteForm(forms.ModelForm):
domainselect = AutoCompleteSelectField(lookup_class= DomainLookup, label='Pick a domain category', required=True,)
def __init__(self, *args, **kwargs):
super(NoteForm, self).__init__(*args, **kwargs)
domaintext = self.instance.domain.title
self.fields['domainselect'].widget = AutoCompleteSelectWidget(DomainLookup , { 'value': self.instance.domain.title } )
def save(self, commit=True):
self.instance.domain = self.cleaned_data['domainselect']
return super(NoteForm, self).save(commit=commit)
class Meta:
model = Note
fields = ('notetext',)
widgets = {
'domain' : AutoCompleteSelectWidget(DomainLookup), }
# VIEW
class EditNoteView(generic.edit.UpdateView):
model = Note
form_class = NoteForm
success_url = "/note/"
def get_queryset(self):
base_qs = super(EditNoteView, self).get_queryset()
return base_qs.filter()
def get_object(self):
object = get_object_or_404(Note,id=self.kwargs['id'])
return object
# TEMPLATE
{% extends "base_sidebar.html" %}
{%block content%}
<form action="" method="post">
{{form.as_p}}
<button type="submit">Save</button>
{% csrf_token %}
{% load selectable_tags %}
{{ form.media.css }}
{{ form.media.js }}
</form>
{%endblock%}
Now, when an existing record is selected for editing via generic.edit.UpdateView in a Modelform, I want to populate the AutocompleteSelectField with the corresponding values ( domain description and id ) formerly saved into the database upon loading the form.
By overwriting the init(self, *args, **kwargs) method of the NoteForm, I was able to get almost this far in the sense that the first HTML input field gets populated.
However, the hidden input value gets set to the same value and pushing the save button results in posting a non valid form as if no domain category was selected.
Here's the page source that is sent back to the Browser:
<p><label for="id_domainselect_0">Pick a domain:</label>
<input data-selectable-allow-new="false" data-selectable-type="text" data-selectable-url="/selectable/domain-domainlookup/" id="id_domainselect_0" name="domainselect_0" type="text" value="politics" />
<input data-selectable-type="hidden" id="id_domainselect_1" name="domainselect_1" type="hidden" value="politics" /></p>
I don't know how to change the context (by setting self.fields['domainselect'].widget) in order to get the title into the domainselect_0 input value and the corresponding pk into the hidden domainselect_1 input value. ?
Thanks for helping me out.
After digging down into the django-selectable and Django code it appears the AutocompleteSelectWidget is based on the Django forms.MultiWidget class.
The Django MultiWidget accepts 1 single value (list) that is decomposed into the values corresponding to the respective 'subWidgets' through a mechanism implemented in a decompress method. ( see https://github.com/mlavin/django-selectable/blob/master/selectable/forms/widgets.py class SelectableMultiWidget )
So, all you have to do is assign a list containing title and id to the widget:
def __init__(self, *args, **kwargs):
super(NoteForm, self).__init__(*args, **kwargs)
self.initial['domainselect'] = [self.instance.domain.title , self.instance.domain.id ]
Related
When I render the empty form it is not attaching any media to it. i.e. the CKEditor is not displayed. The element looks like it is missing the css/js - it's like it doesn't get set up properly.
Note : the other sections are displayed correctly.
Where to start? Problem with Django's Empty Form method? Problem with CKEditor? Me :)
<div class="container">
<button type ="button" class="btn-info btn-lg" id="add_section">Add Section</button>
{{form.media }}
{{form|crispy }}
{{sections.media}}
<div>
{{sections.empty_form}}
</div>
<div id = 'section_management'> {{ sections.management_form }} </div>
{% for section in sections %}
{{ section|crispy }}
{% endfor %}
<button class="btn btn-info ml-2" type="submit">Update</button>
Cancel
</div>
Here's my Forms
class SectionForm(forms.ModelForm):
content = RichTextFormField()
class Meta:
model = Section
fields = ('content',)
empty_permitted=True
def __init__(self, *args, **kwargs):
print('section form called')
super().__init__(*args, **kwargs)
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ('title','category','span')
def __init__(self, *args, **kwargs):
self.is_superuser = kwargs.pop('is_superuser', None)
super().__init__(*args, **kwargs)
if self.is_superuser == False:
self.fields.pop("span")
view code
class ArticleUpdateView(LoginRequiredMixin,UserPassesTestMixin,UpdateView):
template_name = 'articles/ArticleUpdate.html'
form_class = ArticleForm
model = Article
SectionFormSet = inlineformset_factory(Article, Section, form=SectionForm, extra=0, can_delete=False, fields=('content',))
#if i always pass back at least 1 extra section form, I can grab the html for it in Jquery */
#if i do not pass back extra=0 how would i get the html in jquery for the extra form?
def test_func(self):
article = self.get_object()
if self.request.user == article.author or self.request.user.is_superuser :
return True
else:
return False
def get_context_data(self, **kwargs):
print('get context data called update view')
'''
section_form
'''
context = super().get_context_data(**kwargs)
if self.request.POST:
context['sections'] = self.SectionFormSet(self.request.POST,instance=self.object)
else:
context['sections'] = self.SectionFormSet(instance=self.object)
return context
def get_section_form(self): #we know we can access this in the template
return SectionForm()
def save_sections(self):
print('save sections called update view')
try:
context = self.get_context_data()
section_form = context['sections']
if section_form.is_valid():
# section_form.instance = self.object #if im passing instance in the factory, do I need it here to?
section_form.save()
except Exception as e:
print('failed to save section: ' + str(e))
def form_valid(self, form):
print('form valid called update view')
form.instance.author = self.request.user
response = super().form_valid(form) #save article form
self.save_sections()
return response
def get_success_url(self):
return reverse_lazy('index')
Basically, what I've done so far to overcome this problem is by accessing the form directly from the template, bypassing the inlineFormSet to get an empty form....(hope that makes sense).
I go directly to the view :
{{view.get_section_form}}
with this method in the view
def get_section_form(self): #we know we can access this in the template
return SectionForm()
I have subsequently found out I can do this in the template as well :
{{sections.media}}
{{sections.form}}
The above also passes an empty form - with the media filled in- as long as you pass the model form into the factory to start of with.
These are work-arounds for me currently, but would appreciate a proper answer as to why empty_form doesn't work properly.
My further investigation into this was basically comparing what is returned via accessing the formset to return an empty form, or using the modelForm directly.
Django docs :
empty_formĀ¶
BaseFormSet provides an additional attribute empty_form which returns a form instance with a prefix of __prefix__ for easier use in dynamic forms with JavaScript.
If you replace prefix on the generated html -- everything works. No idea why. You can replace it with anything, i.e. prefix1
at which point CKEditor starts to display the formset correctly.
My Area model has an exercise attribute with a ManyToManyField to my Exercise model:
class Area(models.Model):
name = models.CharField(max_length=100)
exercise = models.ManyToManyField(Exercise)
class Exercise(models.Model):
name = models.CharField(max_length=100)
My AreaView displays a list of areas, which each link to their own list of specific exercises, shown by AreaDetailView:
class AreaView(ListView):
model = Area
template_name = 'workouts/areas.html'
class AreaDetailView(DetailView):
model = Area
template_name = 'workouts/exercises.html'
def get_context_data(self, **kwargs):
context = super(AreaDetailView, self).get_context_data(**kwargs)
context['e_form'] = AddExerciseForm
return context
e.g:
areas.html
- abs
- biceps
- cardio
- legs ...
exercises.html
Abs
- Ab-wheel
- Cable-crunch
- Plank ...
Biceps
- Barbell curl
- Cable curl
- Dumbbell curl
AreaDetailView also displays a form which I would like to allow the user to create their own exercises, which will be specific to their corresponding area.
Here is my form:
class AddExerciseForm(forms.ModelForm):
class Meta:
model = Exercise
fields = ['name']
My template:
<form action="{% url 'exercise_add_new' %}" method="post">
{% csrf_token %}
{{ e_form }}
<button type="submit">Save changes</button>
</form>
My url:
path('exercise-add-new', ExerciseFormView.as_view(), name='exercise_add_new'),
And here is my CreateView which is supposed to handle the logic:
class ExerciseFormView(CreateView):
form_class = AddExerciseForm
success_url = '/'
def form_valid(self, form):
form.save()
new_ex = Exercise.objects.latest('id')
area = Area.objects.get(id=1)
area.exercise.add(new_ex)
return super(ExerciseFormView, self).form_valid(form)
This allows me to update the first object in my Area model ok, but I need to adjust the value of the variable area in form_valid so that the current 'id' is updated. For example if I click on 'Biceps' and then complete the form, I want to add an exercise related to 'id=2'
I have tried area = Area.objects.get(id=self.kwargs['id'])and other similar variations but so far nothing I have tried has worked
In ExerciseFormView are you trying to add a new exercise to an area or create a new area?
If adding a new exercise you will have to pass the area-id from the URL something like add_exercise/<area_id>, if doing the latter it should be straightforward.
You have pass area-id in URL you can do like below
path('exercise-add-new/<int:area_id>/', ExerciseFormView.as_view(),
name='exercise_add_new')
Then update your view as below
def form_valid(self, form):
form.save()
area = Area.objects.get(pk=self.kwargs["area_id"])
new_ex = Exercise.objects.latest('id')
area.exercise.add(new_ex)
return super(ExerciseFormView, self).form_valid(form)
Also update template as :
<form method="post">
{% csrf_token %}
{{ e_form }}
<button type="submit">Save changes</button>
</form>
If I have two forms:
class ContactForm(forms.Form):
name = forms.CharField()
message = forms.CharField(widget=forms.Textarea)
class SocialForm(forms.Form):
name = forms.CharField()
message = forms.CharField(widget=forms.Textarea)
and wanted to use a class based view, and send both forms to the template, is that even possible?
class TestView(FormView):
template_name = 'contact.html'
form_class = ContactForm
It seems the FormView can only accept one form at a time.
In function based view though I can easily send two forms to my template and retrieve the content of both within the request.POST back.
variables = {'contact_form':contact_form, 'social_form':social_form }
return render(request, 'discussion.html', variables)
Is this a limitation of using class based view (generic views)?
Many Thanks
Here's a scaleable solution. My starting point was this gist,
https://gist.github.com/michelts/1029336
i've enhanced that solution so that multiple forms can be displayed, but either all or an individual can be submitted
https://gist.github.com/jamesbrobb/748c47f46b9bd224b07f
and this is an example usage
class SignupLoginView(MultiFormsView):
template_name = 'public/my_login_signup_template.html'
form_classes = {'login': LoginForm,
'signup': SignupForm}
success_url = 'my/success/url'
def get_login_initial(self):
return {'email':'dave#dave.com'}
def get_signup_initial(self):
return {'email':'dave#dave.com'}
def get_context_data(self, **kwargs):
context = super(SignupLoginView, self).get_context_data(**kwargs)
context.update({"some_context_value": 'blah blah blah',
"some_other_context_value": 'blah'})
return context
def login_form_valid(self, form):
return form.login(self.request, redirect_url=self.get_success_url())
def signup_form_valid(self, form):
user = form.save(self.request)
return form.signup(self.request, user, self.get_success_url())
and the template looks like this
<form class="login" method="POST" action="{% url 'my_view' %}">
{% csrf_token %}
{{ forms.login.as_p }}
<button name='action' value='login' type="submit">Sign in</button>
</form>
<form class="signup" method="POST" action="{% url 'my_view' %}">
{% csrf_token %}
{{ forms.signup.as_p }}
<button name='action' value='signup' type="submit">Sign up</button>
</form>
An important thing to note on the template are the submit buttons. They have to have their 'name' attribute set to 'action' and their 'value' attribute must match the name given to the form in the 'form_classes' dict. This is used to determine which individual form has been submitted.
By default, class-based views only support a single form per view. But there are other ways to accomplish what you need. But again, this cannot handle both forms at the same time. This will also work with most of the class-based views as well as regular forms.
views.py
class MyClassView(UpdateView):
template_name = 'page.html'
form_class = myform1
second_form_class = myform2
success_url = '/'
def get_context_data(self, **kwargs):
context = super(MyClassView, self).get_context_data(**kwargs)
if 'form' not in context:
context['form'] = self.form_class(request=self.request)
if 'form2' not in context:
context['form2'] = self.second_form_class(request=self.request)
return context
def get_object(self):
return get_object_or_404(Model, pk=self.request.session['value_here'])
def form_invalid(self, **kwargs):
return self.render_to_response(self.get_context_data(**kwargs))
def post(self, request, *args, **kwargs):
self.object = self.get_object()
if 'form' in request.POST:
form_class = self.get_form_class()
form_name = 'form'
else:
form_class = self.second_form_class
form_name = 'form2'
form = self.get_form(form_class)
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(**{form_name: form})
template
<form method="post">
{% csrf_token %}
.........
<input type="submit" name="form" value="Submit" />
</form>
<form method="post">
{% csrf_token %}
.........
<input type="submit" name="form2" value="Submit" />
</form>
Its is possible for one class-based view to accept two forms at a time.
view.py
class TestView(FormView):
template_name = 'contact.html'
def get(self, request, *args, **kwargs):
contact_form = ContactForm()
contact_form.prefix = 'contact_form'
social_form = SocialForm()
social_form.prefix = 'social_form'
# Use RequestContext instead of render_to_response from 3.0
return self.render_to_response(self.get_context_data({'contact_form': contact_form, 'social_form': social_form}))
def post(self, request, *args, **kwargs):
contact_form = ContactForm(self.request.POST, prefix='contact_form')
social_form = SocialForm(self.request.POST, prefix='social_form ')
if contact_form.is_valid() and social_form.is_valid():
### do something
return HttpResponseRedirect(>>> redirect url <<<)
else:
return self.form_invalid(contact_form,social_form , **kwargs)
def form_invalid(self, contact_form, social_form, **kwargs):
contact_form.prefix='contact_form'
social_form.prefix='social_form'
return self.render_to_response(self.get_context_data({'contact_form': contact_form, 'social_form': social_form}))
forms.py
from django import forms
from models import Social, Contact
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit, Button, Layout, Field, Div
from crispy_forms.bootstrap import (FormActions)
class ContactForm(forms.ModelForm):
class Meta:
model = Contact
helper = FormHelper()
helper.form_tag = False
class SocialForm(forms.Form):
class Meta:
model = Social
helper = FormHelper()
helper.form_tag = False
HTML
Take one outer form class and set action as TestView Url
{% load crispy_forms_tags %}
<form action="/testview/" method="post">
<!----- render your forms here -->
{% crispy contact_form %}
{% crispy social_form%}
<input type='submit' value="Save" />
</form>
Good Luck
I have used a following generic view based on TemplateView:
def merge_dicts(x, y):
"""
Given two dicts, merge them into a new dict as a shallow copy.
"""
z = x.copy()
z.update(y)
return z
class MultipleFormView(TemplateView):
"""
View mixin that handles multiple forms / formsets.
After the successful data is inserted ``self.process_forms`` is called.
"""
form_classes = {}
def get_context_data(self, **kwargs):
context = super(MultipleFormView, self).get_context_data(**kwargs)
forms_initialized = {name: form(prefix=name)
for name, form in self.form_classes.items()}
return merge_dicts(context, forms_initialized)
def post(self, request):
forms_initialized = {
name: form(prefix=name, data=request.POST)
for name, form in self.form_classes.items()}
valid = all([form_class.is_valid()
for form_class in forms_initialized.values()])
if valid:
return self.process_forms(forms_initialized)
else:
context = merge_dicts(self.get_context_data(), forms_initialized)
return self.render_to_response(context)
def process_forms(self, form_instances):
raise NotImplemented
This has the advantage that it is reusable and all the validation is done on the forms themselves.
It is then used as follows:
class AddSource(MultipleFormView):
"""
Custom view for processing source form and seed formset
"""
template_name = 'add_source.html'
form_classes = {
'source_form': forms.SourceForm,
'seed_formset': forms.SeedFormset,
}
def process_forms(self, form_instances):
pass # saving forms etc
It is not a limitation of class-based views. Generic FormView just is not designed to accept two forms (well, it's generic). You can subclass it or write your own class-based view to accept two forms.
Use django-superform
This is a pretty neat way to thread a composed form as a single object to outside callers, such as the Django class based views.
from django_superform import FormField, SuperForm
class MyClassForm(SuperForm):
form1 = FormField(FormClass1)
form2 = FormField(FormClass2)
In the view, you can use form_class = MyClassForm
In the form __init__() method, you can access the forms using: self.forms['form1']
There is also a SuperModelForm and ModelFormField for model-forms.
In the template, you can access the form fields using: {{ form.form1.field }}. I would recommend aliasing the form using {% with form1=form.form1 %} to avoid rereading/reconstructing the form all the time.
Resembles #james answer (I had a similar starting point), but it doesn't need to receive a form name via POST data. Instead, it uses autogenerated prefixes to determine which form(s) received POST data, assign the data, validate these forms, and finally send them to the appropriate form_valid method. If there is only 1 bound form it sends that single form, else it sends a {"name": bound_form_instance} dictionary.
It is compatible with forms.Form or other "form behaving" classes that can be assigned a prefix (ex. django formsets), but haven't made a ModelForm variant yet, tho you could use a model form with this View (see edit below). It can handle forms in different tags, multiple forms in one tag, or a combination of both.
The code is hosted on github (https://github.com/AlexECX/django_MultiFormView). There are some usage guidelines and a little demo covering some use cases. The goal was to have a class that feels as close as possible like the FormView.
Here is an example with a simple use case:
views.py
class MultipleFormsDemoView(MultiFormView):
template_name = "app_name/demo.html"
initials = {
"contactform": {"message": "some initial data"}
}
form_classes = [
ContactForm,
("better_name", SubscriptionForm),
]
# The order is important! and you need to provide an
# url for every form_class.
success_urls = [
reverse_lazy("app_name:contact_view"),
reverse_lazy("app_name:subcribe_view"),
]
# Or, if it is the same url:
#success_url = reverse_lazy("app_name:some_view")
def get_contactform_initial(self, form_name):
initial = super().get_initial(form_name)
# Some logic here? I just wanted to show it could be done,
# initial data is assigned automatically from self.initials anyway
return initial
def contactform_form_valid(self, form):
title = form.cleaned_data.get('title')
print(title)
return super().form_valid(form)
def better_name_form_valid(self, form):
email = form.cleaned_data.get('email')
print(email)
if "Somebody once told me the world" is "gonna roll me":
return super().form_valid(form)
else:
return HttpResponse("Somebody once told me the world is gonna roll me")
template.html
{% extends "base.html" %}
{% block content %}
<form method="post">
{% csrf_token %}
{{ forms.better_name }}
<input type="submit" value="Subscribe">
</form>
<form method="post">
{% csrf_token %}
{{ forms.contactform }}
<input type="submit" value="Send">
</form>
{% endblock content %}
EDIT - about ModelForms
Welp, after looking into ModelFormView I realised it wouldn't be that easy to create a MultiModelFormView, I would probably need to rewrite SingleObjectMixin as well. In the mean time, you can use a ModelForm as long as you add an 'instance' keyword argument with a model instance.
def get_bookform_form_kwargs(self, form_name):
kwargs = super().get_form_kwargs(form_name)
kwargs['instance'] = Book.objects.get(title="I'm Batman")
return kwargs
I would like to be able to extract different information in my django form:
That's my form:
<form action="" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>
class InstanceForm(ModelForm):
class Meta:
model = models.BaseAsset
widgets = {
'labels': LabelIconCheckboxSelectMultiple()
}
The model:
class AssetClass(models.Model):
default_labels = models.ManyToManyField(Label, null=True, blank=True)
pass
the M2M reference field
class Label(models.Model):
explanation = models.CharField(null=True, max_length=63)
svgpreview = models.CharField(null=True, max_length=31)
def __unicode__(self):
return unicode(self.explanation)
pass
Now, the HTML code generated by the {{ form.as_p }} is as follows:
<li><label for="id_labels_0"><input type="checkbox" name="labels" value="1" id="id_labels_0" /> Consult owner before using</label></li>
<li><label for="id_labels_1"><input type="checkbox" name="labels" value="2" id="id_labels_1" /> This item is broken</label></li>
Which means it's clearly using the __unicode__ rendering of the model 'Label'. How can I change that behavior in the Select widget, so that it would use a different function to populate it's choices? I'm trying to get it, in the reasonably portable way, to print '<img src="{{label.svgpreview}}" alt="{{label.explanation}}"...>' next to the checkbox?
You will override forms.widgets.CheckboxSelectMultiple class:
This is CheckboxSelectMultiple class and its render function:
class CheckboxSelectMultiple(SelectMultiple):
def render(self, name, value, attrs=None, choices=()):
if value is None: value = []
has_id = attrs and 'id' in attrs
final_attrs = self.build_attrs(attrs, name=name)
output = [u'<ul>']
# Normalize to strings
str_values = set([force_unicode(v) for v in value])
for i, (option_value, option_label) in enumerate(chain(self.choices, choices)):
# If an ID attribute was given, add a numeric index as a suffix,
# so that the checkboxes don't all have the same ID attribute.
if has_id:
final_attrs = dict(final_attrs, id='%s_%s' % (attrs['id'], i))
label_for = u' for="%s"' % final_attrs['id']
else:
label_for = ''
cb = CheckboxInput(final_attrs, check_test=lambda value: value in str_values)
option_value = force_unicode(option_value)
rendered_cb = cb.render(name, option_value)
option_label = conditional_escape(force_unicode(option_label))
output.append(u'<li><label%s>%s %s</label></li>' % (label_for, rendered_cb, option_label))
output.append(u'</ul>')
return mark_safe(u'\n'.join(output))
So what you will do :
class MyCheckboxSelectMultiple(CheckboxSelectMultiple):
def render(self, name, value, attrs=None, choices=()):
#put your code to have custom checkbox control with icon
#...
output.append(u'<li><label%s>%s %s</label></li>' % (label_for, rendered_cb, option_label)) # especially you will be working on this line
#...
Then where you are using widgets=CheckboxSelectMultiple() it will become widgets=MyCheckboxSelectMultiple()
Reading django.forms.models.ModelChoiceField gives a hint:
# this method will be used to create object labels by the QuerySetIterator.
# Override it to customize the label.
def label_from_instance(self, obj):
"""
This method is used to convert objects into strings; it's used to
generate the labels for the choices presented by this object. Subclasses
can override this method to customize the display of the choices.
"""
return smart_unicode(obj)
ok, but how do I override it per-instance of ModelForm - this gets overridden in few places throughout django.forms
Considering the following code:
class InstanceForm(ModelForm):
class Meta:
model = models.BaseAsset
widgets = {
'labels': forms.CheckboxSelectMultiple()
}
def __init__(self, *args, **kwargs):
def new_label_from_instance(self, obj):
return obj.svgpreview
super(InstanceForm, self).__init__(*args, **kwargs)
funcType = type(self.fields['labels'].label_from_instance)
self.fields['labels'].label_from_instance = funcType(new_label_from_instance, self.fields['labels'], forms.models.ModelMultipleChoiceField)
This is somewhat creepy - basically, it's a more bizzare implementation of this:
Override a method at instance level
Please read the comments in the referenced thread to understand why this might be a bad idea in general..
You don't have to do the "creepy" instance-level override to take proper advantage of the documented django.forms.models.ModelChoiceField.label_from_instance() method.
Building on the AssetClass and Label objects in the original post:
class AssetSvgMultiField(forms.ModelMultipleChoiceField):
"""
Custom ModelMultipleChoiceField that labels instances with their svgpreview.
"""
def label_from_instance(self, obj):
return obj.svgpreview
class InstanceForm(forms.ModelForm):
default_labels = AssetSvgMultiField(queryset=Label.objects.all())
class Meta:
model = models.AssetClass
widgets = {
'default_labels': forms.CheckboxSelectMultiple()
}
This is explained in the Django documentation here:
https://docs.djangoproject.com/en/1.9/ref/forms/fields/#django.forms.ModelChoiceField.to_field_name
You can see the ModelChoiceField class calling the method on the field here:
https://github.com/django/django/blob/1155843a41af589a856efe8e671a796866430049/django/forms/models.py#L1174
If you're not overriding choices explicitly, then your code might look like this:
class RectificationAssetMultiField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
return '[{0.pk}] {0.label} ({0.location})'.format(obj)
class RectificationForm(forms.ModelForm):
items = RectificationAssetMultiField(
required=False,
queryset=InspectionItem.objects.all(),
widget=forms.CheckboxSelectMultiple,
label="Non-compliant Assets"
)
class Meta:
model = Rectification
fields = ('ref', 'items', 'status')
Be careful that this will only work if you're not setting choices directly (see _get_choices in the above URL).
If instead you wanted to override choices (for a more efficient result than a queryset, or something better expressed as a ValuesList) then you would have something like this:
class RectificationAssetMultiField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
return '[{0.pk}] {0.label} ({0.location})'.format(obj)
class RectificationForm(forms.ModelForm):
items = RectificationAssetMultiField(
required=False,
queryset=InspectionItem.objects.none(),
widget=forms.CheckboxSelectMultiple,
label="Non-compliant Assets"
)
def __init__(self, *args, **kwargs):
super(RectificationForm, self).__init__(*args, **kwargs)
self.fields['items'].choices = (InspectionItem.objects
.active()
.noncompliant()
.filter(property_id=self.instance.property_id)
.values_list('pk', 'label') # pass a key value pair
)
class Meta:
model = Rectification
fields = ('ref', 'items', 'status')
Don't use {{ form.as_p }} if you don't like that rendering.
Loop over the form instead:
<form action="/contact/" method="post">
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }}: {{ field }}
</div>
{% endfor %}
<p><input type="submit" value="Send message" /></p>
</form>
You are then free to use whatever HTML you want.
From: https://docs.djangoproject.com/en/dev/topics/forms/#looping-over-the-form-s-fields
I'm implementing simple "grade book" application where the teacher would be able to update the grades w/o being allowed to change the students' names (at least not on the update grade page). To do this I'm using one of the read-only tricks, the simplest one. The problem is that after the SUBMIT the view is re-displayed with 'blank' values for the students. I'd like the students' names to re-appear.
Below is the simplest example that exhibits this problem. (This is poor DB design, I know, I've extracted just the relevant parts of the code to showcase the problem. In the real example, student is in its own table but the problem still exists there.)
models.py
class Grade1(models.Model):
student = models.CharField(max_length=50, unique=True)
finalGrade = models.CharField(max_length=3)
class Grade1OForm(ModelForm):
student = forms.CharField(max_length=50, required=False)
def __init__(self, *args, **kwargs):
super(Grade1OForm,self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.id:
self.fields['student'].widget.attrs['readonly'] = True
self.fields['student'].widget.attrs['disabled'] = 'disabled'
def clean_student(self):
instance = getattr(self,'instance',None)
if instance:
return instance.student
else:
return self.cleaned_data.get('student',None)
class Meta:
model=Grade1
views.py
from django.forms.models import modelformset_factory
def modifyAllGrades1(request):
gradeFormSetFactory = modelformset_factory(Grade1, form=Grade1OForm, extra=0)
studentQueryset = Grade1.objects.all()
if request.method=='POST':
myGradeFormSet = gradeFormSetFactory(request.POST, queryset=studentQueryset)
if myGradeFormSet.is_valid():
myGradeFormSet.save()
info = "successfully modified"
else:
myGradeFormSet = gradeFormSetFactory(queryset=studentQueryset)
return render_to_response('grades/modifyAllGrades.html',locals())
template
<p>{{ info }}</p>
<form method="POST" action="">
<table>
{{ myGradeFormSet.management_form }}
{% for myform in myGradeFormSet.forms %}
{# myform.as_table #}
<tr>
{% for field in myform %}
<td> {{ field }} {{ field.errors }} </td>
{% endfor %}
</tr>
{% endfor %}
</table>
<input type="submit" value="Submit">
</form>
Your way of displaying the readonly field is the problem.
Since the student field is disabled, the form submit will not have that as the input, so the error form that is displayed with validation error messages don't get the initial value.
That is why ReadOnly Widget has to be more complex than just being a html disabled field.
Try using a real ReadOnlyWidget, one that overrides _has_changed.
Following is what I use. For instantiation, it takes the original_value and optionally display_value, if it is different.
class ReadOnlyWidget(forms.Widget):
def __init__(self, original_value, display_value=None):
self.original_value = original_value
if display_value:
self.display_value = display_value
super(ReadOnlyWidget, self).__init__()
def _has_changed(self, initial, data):
return False
def render(self, name, value, attrs=None):
if self.display_value is not None:
return unicode(self.display_value)
return unicode(self.original_value)
def value_from_datadict(self, data, files, name):
return self.original_value
I'm stretching myself a little here, so some thoughts:
% Have you sniffed the traffic to see exactly what's being sent between browser and server?
% Do you need to send the student name as a hidden field (your db update thing may assume you want student blank if you don't)?
% Have you looked at the source of your HTML after Python parses it?