I have uploading working just fine, but the problem is, I would like to filter out all file types that are not needed.
I know with html, you can use something like:
<input type="file" name="file" accept="image/*" id="file"> <input type="submit" name="submit" value="Upload Image!">
and in this case, this tells the browser to only show image files in the file dialog box.
I can't figure out how I can add the accept="image/*" to Django's widget.
Any help is most welcomed!
You can change this with the widget attrs as documented here: https://docs.djangoproject.com/en/1.3/ref/forms/widgets/#django.forms.Widget.attrs
from django import forms
class MyForm(forms.Form):
my_file = forms.FileField()
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.fields['my_file'].widget.attrs.update({'accept': 'image/*'})
Or you can use something like django-widget-tweaks to do this with a templatetag.
Related
I have a Django form which has an ImageField in it, and I render the field like this in my template:
{{ form.my_image_field }}
The resulting HTML looks like this:
Currently: ....jpg
<input name="my_image_field-clear" id="my_image_field-clear_id" type="checkbox">
<label for="my_image_field-clear_id">Clear</label><br>
Change:
<input name="my_image_field" id="id_my_image_field" type="file">
I want to render only the <input element, since I am using the bootstra-fileinput library: https://www.npmjs.com/package/bootstrap-fileinput :
<input name="my_image_field" id="id_my_image_field" type="file">
Is it possible to do this without writing the <input> tag manually in the template?
Normally, ImageFields use the ClearableFileInput widget. This widget by default uses the template name django/forms/widgets/clearable_file_input.html.
You can make your own ClearableFileInput widget subclass that uses a different template.
from django.forms.widgets import ClearableFileInput
class MyImageWidget(ClearableFileInput):
template_name = "myapp/my_template.html"
The content of that template may just be simply the <input> part of the default template with the extraneous label and checkbox removed. You can customize it however you need. That might look like
<!-- myapp/my_template.html -->
<input type="{{ widget.type }}" name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>
Then in your form specify the widget for the ImageField
class MyForm(forms.Form):
# ...
my_image_field = forms.ImageField(widget=MyImageWidget)
# ...
Django also provides a FileInput widget which uses a plain template. If that suits your needs simply do
class MyForm(forms.Form):
# ...
my_image_field = forms.ImageField(widget=forms.widgets.FileInput)
Alternatively, you could simply override the default template by creating the django/forms/widgets/clearable_file_input.html file in your project's template directory. However, this would apply the change to the rendering of all fields using the ClearableFileInput widget, so it is not as flexible of an approach.
How does one correctly override the styling of a single item in Django's model form {{form}} call. I cant seem to just overide the defualt behaviour and add my necessary style.
Specifically, I would like to add a simple slider that passes through some text to the view.
I have added an extra field to my form as follows:
class CreateTestForm(forms.ModelForm):
difficulty = forms.CharField()
class Meta:
model = Test
And the following in my html directly:
<form method="POST" action=""> {% csrf_token %}
{{ form|crispy }}
<strong>Difficulty</strong>
<input id="difficulty" type="text" value="" class="slider form-control" data-slider-min="0" data-slider-max="10"
data-slider-step="1" data-slider-value="[0,10]" data-slider-orientation="horizontal"
data-slider-selection="before" data-slider-tooltip="show" data-slider-id="blue">
</br>
<input type='submit' value='Create' class='btn'>
</form>
However, when I render the view, I get two difficulties inputs (one slider and one box). I understand that Django is creating the text box for me, but I assumed, giving my slider the same id would simply override it?
From my belief, I would also have to have the slider defined in the forms.py class for this form, else it is not accessible in cleaned_data in the view. Any ideas?
I'm not sure why you would assume that. But why not define the relevant attributes in the form in the first place, so that they get output automatically?
difficulty = forms.CharField(widget=forms.TextInput(attrs={"class": "slider form-control", "data-slider-min": "0"...}))
Or even better, use the Crispy API to let you declare those attributes (I don't use Crispy myself, but I know it gives you a lot of extra control.)
You can override the default widget, adding your class for styling like so:
from django import forms
class CreateTestform(forms.ModelForm):
class Meta:
model = Test
fields = ['difficulty'] # you don't need to define difficulty again,
# just get it from your model like in this example.
widget = {
'difficulty': forms.CharField(attrs={
'class': 'name-of-your-class',
'other-attribute': 'other-value',
}),
}
You can check for more here.
If you have too much styling to apply to your form, I suggest to render it manually and I know you use cripsy to avoid that, but it's a easier way, without limits, django is supposed to be used for backend, not frontend.
You can even render like this (supposing you have to render difficulty):
<input class="whatever-your-class-is" name="{{ form.difficulty.html_name }}" id="{{ form.difficulty.id_for_label }}">
If you look carefully, you found information about .html_name and .id_for_label, even .value here.
I have an UpdateView which contains a form and an InlineFormetSet that is related to the form model (I simplified the code below):
#models.py
class Note(Model):
content = models.TextField()
class Dialog(Model):
message = models.TextField()
note = modes.ForeignKey(Note)
#views.py
class NoteUpdateView(UpdateView):
model = Note
form_class = NoteForm
def get_context_data(self, **kwargs):
context = super(NoteUpdateView ,self).get_context_data(**kwargs)
self.object = self.get_object()
dialogFormset = inlineformset_factory(Note,
Dialog,
fields='__all__',
extra=0)
dialog = dialogFormset(instance=self.object)
context['dialog'] = dialog
return context
def post(self, request, *args, **kwargs):
form = self.get_form(self.get_form_class())
dialog_form = DialogFormset(self.request.POST, instance=Note.objects.get(id=self.kwargs['pk']))
if (form.is_valid() and dialog_form.is_valid()):
return self.form_valid(form, result_form, dialog_form)
else:
return self.form_invalid(form, result_form, dialog_form)
def form_valid(self, form, result_form, dialog_form):
self.object, created = Note.objects.update_or_create(pk=self.kwargs['pk'], defaults=form.cleaned_data)
dialog_form.save()
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form, result_form, dialog_form):
return self.render_to_response(self.get_context_data(form=form,
result_form=result_form,
dialog_form=dialog_form))
The purpose of NoteUpdateView is to render existing Note and Dialog when a GET request is made tonote/11. A user may delete an existing Dialog, which is not handled by the code above.
To handle delete, I can do the following on POST:
1) fetch all of the dialog records related to the requested Note:
dialogs = Note.objects.filter(pk=self.kwargs['pk'])
2) loop through self.request.POST and see if the formsets contained in the submitted data also exist in the dialogs created above.
3) If a record is dialogs but not submitted via POST, then that dialog is considered to be removed by the user. Thus, preform delete operation.
I am sure I can implement these steps. But since I am not very familiar with Django's formset. I wonder if there is any built-in classes or methods that I should use to automate these steps. What is the Django way of doing what I just described?
Ok, I figured out what the problem was. The problem that I run into is due to the use of django-crispy-forms. Let me explain what happened:
When Django renders InlineFormSet, it's can_delete attribute is set to True automatically. When this attribute is set to True, a hidden input field is inserted into the rendered HTML:
<input type="hidden" name="dialog_set-0-DELETE" id="id_dialog_set-0-DELETE">
I used django-crispy-forms to render my forms so that they are styled with bootstrap3. When rendering inlineformset using crispy-forms, a FormHelper needs to be defined.
This is because when you have multiple inlineformset forms on the page, you will only want one <form> tag surrounds them instead of giving each inlineformset form it's own <form> tag. To do that, I had to define the FormHelper like this:
#models.py
class Dialog(Model):
info1 = models.TextField()
info2 = models.TextField()
#forms.py
class DialogFormSetHelper(FormHelper):
def __init__(self, *args, **kwargs):
super(DialogFormSetHelper, self).__init__(*args, **kwargs)
self.form_tag = False # This line removes the '<form>' tag
self.disable_csrf = True # No need to insert the CSRF string with each inlineform
self.layout = Layout(
Field('info1', rows='3'), # make sure the name of the field matches the names defined in the corresponding model
Field('info2', rows='3')
)
I need django-crispy-forms to set the row number of a textarea tag to be 3. Thus, I had to specifically redefine how my textarea fields look like under Layout. What I didn't realize when using the Layout is that anything that you didn't define in it will not be rendered in the HTML.
From the look of the code, I didn't miss any fields defined in the Dialog model. But, what I didn't realize is that the hidden fields that come with InlineFormSet are not rendered in the HTML unless I specifically define them in the Layout object and in the template. To get formset & inlineformset working properly, you will need the following hidden inputs:
formset.manageform. They look like this in the HTML:
<input id="id_dialog_set-TOTAL_FORMS" name="dialog_set-TOTAL_FORMS" type="hidden" value="1">
<input id="id_dialog_set-INITIAL_FORMS" name="dialog_set-INITIAL_FORMS" type="hidden" value="1">
<input id="id_dialog_set-MIN_NUM_FORMS" name="dialog_set-MIN_NUM_FORMS" type="hidden" value="0">
<input id="id_dialog_set-MAX_NUM_FORMS" name="dialog_set-MAX_NUM_FORMS" type="hidden" value="1000">
The primary key that is associated with each inlineformset form, and a foreign key that the inlineformset refers to. They look like this in HTML:
<input id="id_dialog_set-0-note" name="dialog_set-0-note" type="hidden" value="11"> <!-- This line identifies the foreign key`s id -->
<input id="id_dialog_set-0-id" name="dialog_set-0-id" type="hidden" value="4"> <!-- This line identifies the inlineformset form`s id -->
[A DELETE hidden field when can_delete is set to True] (https://docs.djangoproject.com/en/1.9/topics/forms/formsets/#can-delete). It looks like this in the HTML:
<input type="hidden" name="dialog_set-0-DELETE" id="id_dialog_set-0-DELETE">
In my template, I had the first two covered:
<form method="post" action="{{ action }}" enctype="multipart/form-data" id="note_form">
{% crispy form %}
{# the management_form is covered here #}
{{ dialog.management_form }}
{% for form in dialog %}
<div class="formset-container">
<div class="dialog-title">
{% crispy form dialogHelper %}
</div>
{# the hidden fields are covered here #}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
</div>
{% endfor %}
</form>
What I didn't have is the DELETE hidden input. To add it to the HTML, I had to add it this way in the Layout:
#forms.py
class DialogFormSetHelper(FormHelper):
def __init__(self, *args, **kwargs):
super(DialogFormSetHelper, self).__init__(*args, **kwargs)
self.form_tag = False
self.disable_csrf = True
self.layout = Layout(
Field('info1', rows='3'),
Field('info2', rows='3'),
Field('DELETE') # <- ADD THIS LINE
)
Finally, everything works properly now
The Django way is to check if someone has made a library that handles this for you :-).
So take a look at the exellent django-extra-views and it's InlineFormSetView. I've used it a lot and it works really well. In your case your view becomes something like this:
from extra_views import InlineFormSetView
class NoteUpdateView(InlineFormSetView):
model = Note
inline_model = Dialog
form_class = NoteForm
extra = 0
def get_context_data(self, **kwargs):
context = super(NoteUpdateView ,self).get_context_data(**kwargs)
context['dialog'] = context['formset']
return context
You could skip .get_context_data method as well if you update your template to refer to the formset as "formset" instead.
I'm trying to create a simple WTForms-based admin interface for an SQLAlchemy app, using Jinja2 templates.
I've read the docs of WTForms-Alchemy and I understand that it can auto-generate a form from my model just via a few lines of code, like:
class UserForm(ModelForm):
class Meta:
model = User
My problem is that even though I have this form auto-generated, I found no resource anywhere about how can I make it into a functional HTML page. There are a few snippets about rendering errors for fields, as well as some SO answers mentioning macros for rendering whole fields, but I found absolutely no resource about how to generate a full, functional form automatically.
// I understand that this is something what Flask-Admin might do already, I'm not using Flask so this is not a possibility unfortunately.
WTForms leaves it up to you to figure out how to you want to render out your form after you pass it into your template. The simplest way to render a form would be to just iterate through your form and render the fields. When a field (or its label) is called, it emits HTML.
<form action="/some_url" method="POST">
{% for field in form %}
{{ field.label() }}
{{ field() }}
{% endfor %}
<button type="submit" />
</form>
The macros provided here provide an automated way to generate HTML surrounding these fields.
You can use wtf.quick_form like this, in which case you'll have a totally generic form template. Mark up your db.Model members with info{} properties to set field display names etc
<form method="post" action="/{{route}}">
<fieldset>
{{ wtf.quick_form(form, button_map={'submit':'success'}) }}
<input class="btn btn-success" type="submit" value="Submit" />
<button type="button" class="btn">Cancel</button>
</fieldset>
</form>
Your form definition:
class MyobjectForm(BaseModelForm):
class Meta:
model = Myobject
Then your route handler looks like this:
#app.route('/myobject', methods=('GET', 'POST'))
def myobject_route():
obj = Myobject()
form = MyobjectForm(obj = obj)
if form.validate_on_submit():
form.populate_obj(obj)
db.session.add(obj)
db.session.commit()
return redirect(url_for('index'))
return render_template('form.j2', form=form, title='My Object', route='myobject')
I have a model like below
class Book(models.Model):
name = models.CharField(max_length=56)
picture = ImageField(upload_to='/max/')
So when editing the Book model from templates like below
<form enctype="multipart/form-data" action="{% url 'edit_book' book_id %}" method="post">
{% csrf_token %}
{{book_form.name}}
{{book_form.picture}}
</form>
if the book record already has an image an extra html checkbox has been
Currently:
media/product138ba6ccf0d1408d968577fa7648e0ea/assets/bubble.png
<input id="picture-clear_id" name="picture-clear" type="checkbox" /> <label for="picture-clear_id">Clear</label><br />Change:
<input id="selectedFile" name="picture" type="file" />
So if the book has an image already when it has been created, it also has some checkbox and label before it so how to avoid that checkbox ?
Edit
forms.py
class BookForm(ModelForm):
class Meta:
model = Book
def __init__(self, *args, **kwargs):
super(BookForm, self).__init__(*args, **kwargs)
self.fields['picture'].widget.attrs = {'id':'selectedFile'}
I'm a little surprised to be honest, because what you describe looks like ClearableFileInput widget, while according to the documentation, it is FileInput that is used as a default widget.
Still. Try explicitly choosing FileInput:
from django.forms import ModelForm, FileInput
class BookForm(ModelForm):
class Meta:
model = Book
widgets = {
'picture': FileInput(),
}
def __init__(self, *args, **kwargs):
super(BookForm, self).__init__(*args, **kwargs)
self.fields['picture'].widget.attrs = {'id':'selectedFile'}
Update: I'm not surprised anymore. I investigated the issue, and it turned out there simply was a mistake in Django Docs, now corrected. ClearableFileInput is the default widget, so you need to set FileInput explicitly, as shown above.