I have a Django input slider defined as follows:
#widgets.py
from django.forms.widgets import NumberInput
class RangeInput(NumberInput):
input_type = 'range'
#forms.py
from polls.widgets import RangeInput
class VoteForm(forms.ModelForm):
class Meta:
#model = CC_Responses
model = CC_Resp_NoFK
fields = ['Person_ID', 'Test_date', 'Response_value']
# following added from SliderModelForm
widgets ={
'Response_value':RangeInput
}
which is “processed” by the view
def p2vote(request,q_id):
CC_question = get_object_or_404(CC_Questions, pk=q_id)
#
if request.method == 'POST':
form = VoteForm(request.POST)
if form.is_valid():
item = form.save(commit=False)
item.Q_ID = q_id
item.save()
return redirect('/polls/p2')
else:
formV = VoteForm()
return render(request, 'pollapp2/vote.html', {'var_name':CC_question,'form' : VoteForm()})
and in the template I have the inline CSS
<style>
/* Following CSS is used by the input slider (range) since Django assigns id value of id_Response_value */
/*See: https://stackoverflow.com/questions/110378/change-the-width-of-form-elements-created-with-modelform-in-django */
#id_Response_value{width:300px;}
</style>
which is associated with the slider/range
<label for="Response_value">Response value</label>
{% render_field form.Response_value rows="1" class="form-control" %}
I obtained id_Response_value by looking at the source of the rendered HTML in the browser – I guess I could explicitly set an ID. All the above works exactly as I want. I can control the width of the range slider using CSS.
Now, I believe, inline CSS is a “bad thing”, so as a step to improve my code I’m trying to associate the attribute “more directly” with the slider.
In Django documentation in the section: https://docs.djangoproject.com/en/4.0/topics/forms/modelforms/#overriding-the-default-fields
there is the following example:
from django.forms import ModelForm, Textarea
from myapp.models import Author
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ('name', 'title', 'birth_date')
widgets = {
'name': Textarea(attrs={'cols': 80, 'rows': 20}),
}
The dictionary attrs created looks like it has CSS “stuff” in it, so I coded in widgets in the model form in forms.py
from polls.widgets import RangeInput
class VoteForm(forms.ModelForm):
class Meta:
model = CC_Resp_NoFK
fields = ['Person_ID', 'Test_date', 'Response_value']
widgets ={
'Response_value':RangeInput(attrs={'width': '1000px'}),
}
I tried both:
attrs={'width': '1000px'}
and
attrs={'width': 1000}
Neither changed the width of the range slider. Is what I am doing possible in the (model)form? Have I just got a problem with what I am coding in attrs?
I may have misunderstood but I see something like the following promoted as what needs to go in the ModelForm
def __init__(self, *args, **kwargs):
super(ProductForm, self).__init__(*args, **kwargs) # Call to ModelForm constructor
self.fields['long_desc'].widget.attrs['cols'] = 10
self.fields['long_desc'].widget.attrs['rows'] = 20
No. Really? To change the attributes?
Related
I am trying to get a file upload form field working in Django and the part I am having problems with is dynamically changing the form field required attribute. I have tried using "self.fields['field_name'].required=True' in the init method of the form but that isn't working for me.
I have looked at Django dynamically changing the required property on forms but I don't want to build several custom models and a custom render function for one form as surely it must be easier than that.
The reason I am trying to do this is because when a django form validates and has errors it doesn't pass any uploaded files back to the browser form for reediting. It will pass text areas and text inputs that didn't validate back to the form for reediting but not file uploads. I thought if I made the file upload fields mandatory for the first time the record is created mandatory and for subsequent times make them optional. That is basically what I am trying to do.
So here is what I have been trying so far:
In forms.py
from django.forms import fields
from .widgets import PDFUploadWidget, PlainTextWidget
class WQPDFField(fields.Field):
widget = PDFUploadWidget
def widget_attrs(self, widget):
attrs = super().widget_attrs(widget)
attrs['label'] = self.label
return attrs
def clean(self, *args, **kwargs):
return super().clean(*args, **kwargs)
In widgets.py
from django.forms import widgets
from django.utils.safestring import mark_safe
# We subclass from HiddenInput because we want to suppress the printing
# of the label and prefer to print it ourselves.
class PDFUploadWidget(widgets.HiddenInput):
template_name = 'webquest_widgets/widgets/pdf_upload.html'
input_type = 'file'
def __init__(self, *args, **kwargs):
style = 'visibility:hidden'
attrs = kwargs.pop('attrs', None)
if attrs:
attrs['style'] = style
else:
attrs = {'style':style}
attrs['accept'] = '.pdf'
print (attrs)
super().__init__(attrs)
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
return context
#property
def is_hidden(self):
return True
class Media:
css = { 'all': ( 'css/pdfupload.css', ) }
js = ('js/pdfupload.js', )
and finally in forms.py
class WorkWantedForm(forms.Form):
category = forms.ChoiceField(choices=CHOICES)
about = forms.CharField(label="About Yourself", widget=forms.Textarea())
static1 = WQStaticField(text="Enter either phone or email")
phone = forms.CharField(required=False)
email = forms.EmailField(required=False)
cv = WQPDFField(label="Upload CV")
supporting_document = WQPDFField(label="Supporting Document (optional)", required=False)
I am not sure how to pass the "required" attribute to the custom field class after the initialisation of the form but before rendering the form as HTML.
I have an extended admin model that creates action buttons. I have created a view to do pretty much the same thing. I have used tables2 and everything is just fine except for the actions column. I cannot find a way to generate the same button in the table. Is there a way to do this at all?
tables.py
from .models import Ticket
import django_tables2 as tables
'''from .admin import river_actions, create_river_button'''
class TicketTable(tables.Table):
class Meta:
model=Ticket
template_name='django_tables2/table.html'
fields = ('id','subject','request_type','material_type','productline','business','measurement_system',
'created_at','updated_at','status','river_action','project') # fields to display
attrs = {'class': 'mytable'}
'''attrs = {"class": "table-striped table-bordered"}'''
empty_text = "There are no tickets matching the search criteria..."
admin.py (the part that includes the model etc)
# Define a new User admin to get client info too while defining users
class UserAdmin(BaseUserAdmin):
inlines = (UserExtendInline, )
def create_river_button(obj,proceeding):
return '''
<input
type="button"
style=margin:2px;2px;2px;2px;"
value="%s"
onclick="location.href=\'%s\'"
/>
'''%(proceeding.meta.transition,
reverse('proceed_ticket',kwargs={'ticket_id':obj.pk, 'next_state_id':proceeding.meta.transition.destination_state.pk})
)
class TicketAdmin(admin.ModelAdmin):
list_display=('id','client','subject','request_type','material_type','productline','business','measurement_system', \
'created_at','updated_at','created_by','status','river_actions')
#list_display_links=None if has_model_permissions(request.user,Ticket,['view_ticket'],'mmrapp')==True else list_display
#list_display_links=list_display #use None to remove all links, or use a list to make some fields clickable
#search_fields = ('subject','material_type')
list_filter=[item for item in list_display if item!='river_actions'] #exclude river_actions since it is not related to a field and cannot be filtered
#Using fieldset, we can control which fields should be filled by the user in the ADD method. This way, created_by will be the
#logged in user and not a drop down choice on the admin site
fieldsets = [
(None, {
'fields': ('client','subject','description','request_type','material_type', \
'productline','business','measurement_system', 'project')
} ), #to make some field appear horizontal, put them into a []
]
formfield_overrides = {
models.CharField: {'widget': TextInput (attrs={'size':'40'})},
models.TextField: {'widget': Textarea(attrs={'rows':4, 'cols':80})},}
def get_list_display(self, request):
self.user=request.user
return super(TicketAdmin,self).get_list_display(request)
def river_actions(self,obj):
content=""
for proceeding in obj.get_available_proceedings(self.user):
content+=create_river_button(obj, proceeding)
return content
river_actions.allow_tags=True
#override save_model method to save current user since it is not on the admin page form anymore
def save_model(self, request, obj, form, change):
if not change:
# the object is being created and not changed, so set the user
obj.created_by = request.user
obj.save()
views.py
def display_tickets(request):
table = TicketTable(Ticket.objects.all())
RequestConfig(request).configure(table)
'''table = CustomerTable(Customer.objects.filter(self.kwargs['company']).order_by('-pk'))'''
return render(request,'mmrapp/ticket_display.html',{'table':table})
buttons created in admin page:
table created using tables2 in views missing buttons:
You must pass empty_values=() to the column, because by default, django-tables2 only renders the column if the value is not contained in empty_values for that column.
import django_tables2 as tables
from .admin import river_actions, create_river_button
from .models import Ticket
class TicketTable(tables.Table):
river_action = tables.Column(empty_values=())
class Meta:
model=Ticket
template_name='django_tables2/table.html'
fields = (
'id', 'subject', 'request_type', 'material_type', 'productline', 'business', 'measurement_system',
'created_at', 'updated_at', 'status', 'river_action', 'project'
) # fields to display
attrs = {'class': 'mytable'}
empty_text = "There are no tickets matching the search criteria..."
def render_river_action(self, record):
return create_river_button(record, ...)
This is also documented as Table.render_foo methods
GOAL: Send a dictionary of data to a form, to be used in a dropdown boxself.
Views.py
form = FormsdbForm(initial={'user': default_state})
# (to set the default value of the 'user' field)
Forms.py
class FormsdbForm(forms.ModelForm):
ROOMLIST = (('roomID_abcdefghi','Room ABC'),
('roomID_jklmnopqr','Room JKL'))
roomid = forms.ChoiceField(required=False, choices=ROOMLIST)
class Meta:
model = Formsdb
fields = ('user', 'uniqueid', 'roomid')
The above setup displays a form where the field 'roomid' is a dropdown box showing to entries:
Room ABC
Room JKL
After saving, the database is populated with the matching 'RoomID_xxxxxxxxx'
Perfect so far!
In my Views.py I have a dictionary (that I can easily convert into a list-of-lists) with the data that is now statically configured in Forms.py (ROOMLIST).
QUESTION: How can I pass this dictionary (or list) to the form so it displays a dropdown box with choices?
This would replace the current "ROOMLIST" variable and it could easily contain 400-600 entries.
The view:
from django.views.generic import FormView
class FormsdbView(FormView):
# ...
def get_form_kwargs(self):
kwargs = super(FormsdbView, self).get_form_kwargs()
ROOMLIST = (('roomID_abcdefghi','Room ABC'),
('roomID_jklmnopqr','Room JKL'))
kwargs['roomlist'] = ROOMLIST
return kwargs
If you're not using FormView, you might also do form = FormsdbForm(initial={'user': default_state}, roomlist=ROOMLIST)
The form:
from django import forms
class FormsdbForm(forms.ModelForm):
roomid = forms.ChoiceField(required=False)
# ...
def __init__(self, *args, **kwargs):
self.roomlist = kwargs.pop('roomlist', None)
super(FormsdbForm, self).__init__(*args, **kwargs)
self.fields['roomid'].choices = self.roomlist
I have looked around quite a bit but can't quite figure out how to make this work. I basically have a Document form and an Item inlineformset in a view, and I need to perform some validation dependent on the field values in each form. For example, if the Item's copyright_needed field is YES then the Document's account field is required.
How can I pass a reference to the Document form, so that inside ItemForm's clean method, I can look at the Document form's cleaned_data? I'm trying to use curry, as I've seen recommended in other SO answers, but it's not working quite right.
Models.py
class Document(models.Model):
account = models.CharField(max_length=22, blank=True, null=True)
class Item(models.Model):
copyright_needed = models.CharField(max_length=1)
# Document foreign key
document = models.ForeignKey(Document)
It's the ItemForm clean method that shows what I'd like to accomplish, and the error I'm getting.
Forms.py -- EDIT - added init to ItemForm
class ItemForm(forms.ModelForm):
class Meta:
model = Item
fields=[..., 'copyright_needed' ]
def __init__(self, *args, **kwargs):
self.doc_form = kwargs.pop('doc_form')
super(ItemForm, self).__init__(*args, **kwargs)
def clean(self):
cleaned_data = super(ItemForm, self).clean()
msg_required = "This field is required."
cr = cleaned_data.get("copyright_needed")
# This line generates this error: DocumentForm object has no attribute cleaned_data
acct_num = self.doc_form.cleaned_data.get("account")
if cr and cr == Item.YES:
if not acct_num:
self.doc_form.add_error("account", msg_required)
return cleaned_data
class DocumentForm(forms.ModelForm):
...
account = forms.CharField(widget=forms.TextInput(attrs={'size':'25'}), required=False)
class Meta:
model = Document
fields = [ ..., 'account' ]
views.py
def create_item(request):
# create empty forms
form=DocumentForm()
ItemFormSet = inlineformset_factory(Document, Item,
form=ItemForm,
can_delete=False,
extra=1 )
# This is my attempt to pass the DocumentForm to each ItemForm, but its not working
ItemFormSet.form = staticmethod(curry(ItemForm, doc_form=form))
item_formset=ItemFormSet(instance=Document())
if request.POST:
d = Document()
form=DocumentForm(request.POST, instance=d)
if form.is_valid():
new_document=form.save(commit=False)
item_formset=ItemFormSet(request.POST, instance=new_document)
if item_formset.is_valid():
new_document.save()
new_item=item_formset.save()
return HttpResponseRedirect(...)
item_formset=ItemFormSet(request.POST)
return render(request,...)
I'm not even sure what the view is doing - it looks like you're confused on the role of the inlineformset and curry. Firstly, you're currying the init method of ItemForm with the doc_form, but you haven't written an init.
Secondly, it looks like you want to be able to edit the Items inside the Document form. So you need the modelformset_factory, and pass in a custom Formset, on which you write a clean method, that has access to everything you need.
from django.forms.models import modelformset_factory
ItemFormSet = modelformset_factory(Item, form=ItemForm, formset=MyCustomFormset)
then in your customformset -
class MyCustomFormset(BaseInlineFormset):
def clean():
super(MyCustomFormset, self).clean()
for form in self.forms:
#do stuff
Note the clean method on each ItemForm has already been called - this is similar to writing your own clean() on a normal modelform.
EDIT:
OK, so ignore the formset clean, I misunderstood. Just make your document form in the view, pass it along with the formset, then put them all in the same form tag.
<form method="post" action=".">
{%for field in doc_form %}
{{field}}
{%endfor%}
{%for form in formset%}
{{form.as_p}}
{%endfor%}
</form>
Then you have access to all the fields in your request.POST, and you can do whatever you want
doc_form = DocumentForm(request.POST)
formset = ItemFormSet(request.POST)
if all([doc_form.is_valid(), formset.is_valid()]):
#do some stuff
I'm using django admin + grappelli + tinymce from grappelli.
It works perfect, but I can't figure out how to make an admin form with textareas and apply tinymce only for part of them.
For example, I've two fields: description and meta-description. I need tinymce only for description and I need textarea without tinymce for meta-descrption.
tinymce in admin is enabled via form like this:
class AdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
"""Sets the list of tags to be a string"""
instance = kwargs.get('instance', None)
if instance:
init = kwargs.get('initial', {})
kwargs['initial'] = init
super(AdminForm, self).__init__(*args, **kwargs)
class Media:
js = (
settings.STATIC_URL + 'grappelli/tinymce/jscripts/tiny_mce/tiny_mce.js',
settings.STATIC_URL + 'grappelli/tinymce_setup/tinymce_setup.js',
)
and enabled in admin.py:
class ModelAdmin(admin.ModelAdmin):
form = AdminForm
It looks like behaviour is described in the beginning of tinymce init:
tinyMCE.init({
mode: 'textareas',
theme: 'advanced',
skin: 'grappelli',
...
Is there a way to solve my issue?
By setting the mode to textareas, it won't give you any sort of selector control as to which one it applies the editor to. You'll most likely need to drop the mode setting and go with selector: http://www.tinymce.com/wiki.php/Configuration:selector
The django-tinymce config options are just a mirror for TinyMCE's settings.
On a field by field basis you can use this technique providing a custom ModelForm:
class XAdminForm(forms.ModelForm):
name = forms.CharField(label='Name', max_length=100,
widget=forms.TextInput(attrs={'size': '100'}))
something = forms.CharField(label='Something', max_length=SOME_MAX_LENGTH,
widget=forms.Textarea(attrs={'rows': '10', 'cols': '100'}))
note = forms.CharField(label='Note', max_length=NOTE_MAX_LENGTH,
widget=forms.Textarea(attrs={'class': 'ckeditor'}))
class Meta:
model = x
class Meta:
model = x
class XAdmin(admin.ModelAdmin):
model = X
form = XAdminForm
class Media:
js = ('/static/js/ckeditor/ckeditor.js',)
admin.site.register(X, XAdmin)