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.
Related
I'm using a ModelMultipleChoiceField to allow users to select certain model objects to delete. It works fine when a single object is selected, but when multiple objects are selected then a validation error is triggered: "Ensure this value has at most 50 characters". The 50 character limit is from the underlying model's max_length attribute. I'm not sure why this validation is happening at all since I am selecting existing model objects, and even less sure why they are combining the character lengths of all my selections instead of validating each selection individually. I've also noticed that approximately 20 extra characters are being counted for each object selected when totalling the character length. Any help is appreciated.
Here is my code:
Model:
class Template(models.Model):
# Relationships
user = models.ForeignKey("users.CustomUser", on_delete=models.CASCADE)
# Fields
name = models.CharField(max_length=50)
description = models.TextField(max_length=250)
docx_file = models.FileField(("DOCX File"),
upload_to=user_directory_path,
validators=[FileExtensionValidator(allowed_extensions=['docx'])])
created = models.DateTimeField(auto_now_add=True, editable=False)
last_updated = models.DateTimeField(auto_now=True, editable=False)
def __str__(self):
return self.name
Form:
class TemplateChoiceDelete(forms.ModelForm):
name = forms.ModelMultipleChoiceField(queryset=Template.objects.all())
class Meta:
model = Template
fields = ['name']
# Limit results to templates owned by the current user
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(TemplateChoiceDelete, self).__init__(*args, **kwargs)
self.fields['name'].queryset = Template.objects.filter(user=user)
View: (ignore the filter code, that is a work in progress relating to another feature)
def manage_templates(request):
f = TemplateFilter(request.GET, queryset=Template.objects.filter(user=request.user))
if request.method == 'GET':
choice_form = TemplateChoiceDelete(request.GET, user=request.user)
print(choice_form)
if choice_form.is_valid():
choice_form.cleaned_data['name'].delete()
else:
form = TemplateChoiceDelete
return render(request, 'docs/manage_templates.html', {'filter': f, 'choice_form': choice_form})
Template: (again, please ignore the code relating to filters)
{% block content %}
<p> <b> Search </b> </p>
<form method="get">
{{ filter.form.as_p }}
<input type="submit" value="Search" />
</form>
{% for obj in filter.qs %}
{{ obj.name }} | {{ obj.description }}<br />
{% endfor %}
<br>
<p><b> Delete Templates </b></p>
<form method="GET">
{{ choice_form.as_p }}
<input type="submit" onclick="return confirm('Are you sure? This will delete the selected template(s)')" value="Delete">
</form>
{% endblock %}
The error
This is not a ModelForm, if you use a ModelForm, it will of course "inherit" certain validators from the model. This is a simple Form, so:
class TemplateChoiceDelete(forms.Form):
name = forms.ModelMultipleChoiceField(queryset=Template.objects.all())
# no Meta
# Limit results to templates owned by the current user
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super().__init__(*args, **kwargs)
self.fields['name'].queryset = Template.objects.filter(user=user)
A ModelForm is a Form that is tailored towards a certain model to create or update a model instance, not to edit items that have "something to do" with that model.
Note: Section 9 of the HTTP protocol
specifies that requests like GET and HEAD should not have side-effets, so you
should not change entities with such requests. Normally POST, PUT, PATCH, and
DELETE requests are used for this. In that case you make a small <form> that
will trigger a POST request, or you use some AJAX calls.
i want to create i simple django image processing .first i have create correct a django auth and multi upload images in my app.
now i want the user can select one self image from a list django form where i create and that image i get for my processing.i create something but not work.
i take that error :
'MyModelForm' object has no attribute 'user'
here the code :
views.py
#login_required(login_url="login/")
def myview(request):
Myf = MyModelForm(request.user,request.POST)
return render(request,'home.html',{'Myf':Myf})
forms.py
class MyModelForm(ModelForm):
def __init__(self, *args, **kwargs):
if 'user' in kwargs:
self.user = kwargs.pop('user')
super(MyModelForm, self).__init__(*args, **kwargs)
choices = [(obj.id, obj.upload.url) for obj in MyModel.objects.filter(user=self.user)]
self.fields['upload'].widget = Select(choices=choices)
class Meta:
model = MyModel
fields = ('upload',)
if i replace Myf = MyForm(request.user,request.POST) with Myform = MyModelForm(user=request.user) then i think list worked like this
image list
but i cant take select image for image processing.
html :
<form class="" action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ Myform}}
<input type="submit" name="" value="Submit">
</form>
any idea ?
Your MyForm class does not have user in the __init__ method's signature, so you need to provide it as a keyword argument when instantiating the form:
MyForm(user=request.user,data=request.POST)
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 have a from containing some fields, but my css class applies to all the fileds except the EmailField. I've also tried sender.widget.attrs.update({'class':"contatct-form"}) and it still doesn't work (just change the size of field). Does anybody knows what the problem is? as all of my searches were unsuccessful.
form:
from django import forms
class NameForm(forms.Form):
your_name = forms.CharField(initial='Your name', max_length=100)
sender = forms.EmailField()
#sender.widget.attrs.update({'class':"contatct-form"})
message = forms.CharField(widget=forms.Textarea)
template:
<div class="contatct-form">
<form action="" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="send" />
</form>
</div>
The problem you have probably is because you have not assigned any widget to your EmailField(), change to this (like #Todor said) should work:
...
sender = forms.EmailField(
widget=forms.EmailInput(attrs={'class': 'contatct-form'})
)
If this doesn't work for whatever reason (probably wrong css styling), you can just change the styling in your css/class directly like so:
div.contatct-form form input[type=email] {
/* your changes here... */
}
Hope this helps.
We have to do that in the init method for one field or all fields with for loop.
class NameForm(forms.Form):
def __init__(*args, **kwargs):
super(NameForm, self).__init__(*args, **kwargs)
self.fields['sender'].widget.attrs.update({'class':"contatct-form"})
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.