I'm trying to restrict file type, size and extension that can be uploaded in a form. The functionality seems to work, but the validation error messages are not showing. I realize that if file._size > 4*1024*1024 is probably not the best way - but I'll deal with that later.
Here's the forms.py:
class ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = ['name', 'description', 'url', 'product_type', 'price', 'image', 'image_url', 'product_file']
labels = {
'name': 'Product Name',
'url': 'Product URL',
'product_type': 'Product Type',
'description': 'Product Description',
'image': 'Product Image',
'image_url': 'Product Image URL',
'price': 'Product Price',
'product_file': 'Product Zip File',
}
widgets = {
'description': Textarea(attrs={'rows': 5}),
}
def clean(self):
file = self.cleaned_data.get('product_file')
if file:
if file._size > 4*1024*1024:
raise ValidationError("Zip file is too large ( > 4mb )")
if not file.content-type in ["zip"]:
raise ValidationError("Content-Type is not Zip")
if not os.path.splitext(file.name)[1] in [".zip"]:
raise ValidationError("Doesn't have proper extension")
return file
else:
raise ValidationError("Couldn't read uploaded file")
...and here's the view I'm using for that form:
def post_product(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = ProductForm(data = request.POST, files = request.FILES)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
product = form.save(commit = False)
product.user = request.user
product.likes = 0
product.save()
# redirect to a new URL:
return HttpResponseRedirect('/products')
What am I missing?
In your view, you are doing a redirect regardless of whether or not the form is valid - so there is nowhere for Django to show form errors.
The normal way to do this would be to re-render the form when is_valid() is False:
if form.is_valid():
# process the data in form.cleaned_data as required
product.save()
# redirect to a new URL - only if form is valid!
return HttpResponseRedirect('/products')
else:
ctx = {"form": form}
# You may need other context here - use your get view as a template
# The template should be the same one that you use to render the form
# in the first place.
return render(request, "form_template.html", ctx}
You may want to consider using a class-based FormView for this, as it handles the logic of re-rendering forms with errors. This is simpler and easier than writing two separate get and post views to manage your form. Even if you don't do that, it will be easier to have a single view that handles both GET and POST for the form.
Related
I have a view that has a simple "save and add another" functionality, that redirects the user to the same page after submit the form.
View:
def new_planning(request):
form = PlanningForm(request.POST)
if form.is_valid():
form.save()
if 'another' in request.POST:
messages.success(request, ('Success!'))
return redirect('new_planning')
else:
return redirect('list_planning')
return render(request, 'pages/planning/new_planning.html', context={
'form': form,
})
Form:
class PlanningForm(forms.ModelForm):
accountplan = ModelChoiceField(
queryset=AccountsPlan.objects.filter(active=True).order_by('code'),
)
month = forms.DateField(
required=True,
error_messages={'required': '', },
)
amount = forms.DecimalField(
max_digits=9,
decimal_places=2,
required=True,
validators=[
error_messages={'required': '', },
)
class Meta:
model = Planning
fields = '__all__'
The function works as expected and after the submit, the same page is rendered with a blank form. What I want is to keep just the "amount" field blank and keep the data typed in the "accountplan" and "month" fields. Is there a way to do this?
I read about instance in the docs, but it doesn't seem to be what I looking for, since I don't want to get the data from the database (if that's possible), but simply keep the last inputs typed in both fields.
If you rewrite the "ModelForm" to a "Model" class, you can get the values of the posted datas, and can be rendered to the page.
For example:
# views.py
def ContactPageView(request):
if request.method == "POST":
email = request.POST.get('email')
message = request.POST.get('message')
message_element = ContactFormObject(email=email, message=message)
message_element.save()
else:
name, message = '', ''
form_data = name, message
return render(request, 'contact.html', {'form_data': form_data})
# models.py
class ContactFormObject(models.Model):
email = models.CharField(max_length=100) #....
ModelForm is more comfortable, but I don't recommend it if you have extra 10 minutes to code some HTML in order to the possibilities of more customization.
here I am using model forms and trying to make my placeholder dynamic.
my approach is to take request data, pass it into widgets with f string.
what I am trying to achieve is
{'placeholder': f"commenting as {request.user.username}"}
HERE IS MY CODE.
class CommentForm(ModelForm):
class Meta:
model = Comment
fields = ("body",)
widgets = {
"body": forms.TextInput(
attrs={
"placeholder": "Enter your comment",
"class": "comment-form-text",
}
),
}
labels = {
"body": "",
}
This is how I usually pass the request object in a form.
Note: all you need is the CommentForm.__init__ and calling it with CommentForm(request.POST, request=request)
I just added the custom save, but commented it out, to show you can also access it there and do some cool things! :-)
forms.py
class CommentForm(ModelForm):
class Meta:
model = Comment
fields = ("body",)
widgets = {
"body": forms.TextInput(
attrs={
"class": "comment-form-text",
}
),
}
labels = {
"body": "",
}
def __init__(self, *args, **kwargs):
# # Keeping track of if it's an edit form or not ( Not Required, but handy )
# self.is_edit = True if 'instance' in kwargs else False
# Store Request Object
self.request = kwargs.pop('request') if 'request' in kwargs else None
super(CommentForm, self).__init__(*args, **kwargs)
# You can add *Any* custom attribute here to any field
self.fields['body'].widget.attrs={'placeholder': 'commenting as {0}'.format(self.request.user.username)}
# # Just showing that you can also use it in a Custom Save Method :-)
# def save(self, commit=True):
# obj = super(CommentForm, self).save(commit=False)
#
# # Note: Keeping track of **if** it's an edit so we don't re-add to the field!
# if not self.is_edit:
# # Use Request to fill a field (New)
# obj.creator = request.user
# else:
# # Use request to fill a field (edit)
# obj.last_editor = request.user
views.py
def commentformview(request):
form = CommentForm(data=request.POST or None, request=request)
if request.method == 'POST':
if form.is_valid():
form.save()
# redirect
data = {
'form': form,
}
return render(request, 'commentform.html', data)
I am using a modelformset_factory in Django to have a user fill out an unknown number of fields.
I have made the fields required but in the HTML rendering Django does not add required to the HTML element, looking around online this seems to be a common issue but I have not seen any valid answers that apply for what I want and I feel like this should be simple.
How do I make Django add the required tag to appropriate HTML elements for a Formset?
class ItemForm(forms.ModelForm):
class Media:
js = (formset_js_path,)
class Meta:
model = PurchaseOrderItems
fields = ['name', 'vendor', 'quantity', 'price', 'description']
labels = {
'name': 'Item',
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# self.fields['description'] .widget.attrs['required'] = 'required'
self.empty_permitted = False
self.fields['description'] = forms.CharField(
required=False,
label='Description',
)
def clean(self):
"""
:return:
"""
cleaned_data = super().clean()
# print(cleaned_data)
ItemFormSet = modelformset_factory(
PurchaseOrderItems,
form=ItemForm,
extra=0,
min_num=1,
validate_min=True,
can_delete=True,
)
Here is the HTML rendered for the name field, no required yet in my model it certainly is, so if this form is submitted I get DB errors because of empty values:
<input type="text" name="form-0-name" maxlength="150" class="form-control" id="id_form-0-name">
According to the release notes:
Required form fields now have the required HTML attribute. Set the new
Form.use_required_attribute attribute to False to disable it. The
required attribute isn’t included on forms of formsets because the
browser validation may not be correct when adding and deleting
formsets.
So if you want to disable, from your view you must submit the form in this way. This will affect all your fields.
form = ItemForm(use_required_attribute=False)
However, if you only want to affect some you must do the previous step and also in your form add this
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['name'].widget.attrs.update({'required': ''})
self.fields['vendor'].widget.attrs.update({'required': ''})
self.fields['quantity'].widget.attrs.update({'required': ''})
self.fields['price'].widget.attrs.update({'required': ''})
self.fields['description'].widget.attrs.update({'required': 'False'})
On the other hand I see that you are not using widgets in your form you should also use them to make it work.
widgets = {
'name': forms.TextInput(),
'vendor': forms.TextInput(),
'quantity': forms.TextInput(),
'price': forms.TextInput(),
'description': forms.TextInput(),
}
I put all the fields as TextInput, but look for what is indicated according to the type of data here Field types.
I have created a login in page and now want to redirect the user to a page to add additional user information. How do I ensure that the user is "logged in" and that the information is added to that user's profiles? I know I need to use something like user=User.objects.get(email=email) to identify the user but I don't want to have the user enter their email on every single page to identify themselves. Is there something I can pass to keep the user Logged-in?
Here is the view:
def add_user_profile(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = AddUserProfileForm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
# ...
# redirect to a new URL:
return HttpResponseRedirect('/summary/')
# if a GET (or any other method) we'll create a blank form
else:
form = AddUserProfileForm()
return render(request, 'registration/add_user_profile.html', {'form': form})
Here is the form:
class AddUserProfileForm(forms.ModelForm):
this_year = datetime.date.today().year
Years = range(this_year-70, this_year+1)
required_css_class = 'required'
first_name = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'First Name'}), label="")
middle_name = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'Middle Name'}), label="")
last_name = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'Last Name'}), label="")
date_of_birth = forms.DateField(widget=forms.extras.widgets.SelectDateWidget(years=Years))
Primary_address = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'Primary address'}), label="")
Primary_address_zipcode = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'Zip Code'}), label="")
class Meta:
model = User
fields = ('first_name', 'middle_name', 'last_name', 'date_of_birth', 'Primary_address', 'Primary_address_zipcode')
Here is the url.py
url(r'^add_user_profile/$',
views.add_user_profile,
name='add_user_profile'),
You just need to create a view which will deal with request.user. If your update_profile functionality is rather simple you may take a look at UpdateView. You just need to overwrite get_object() to look like this:
def get_object(self, queryset=None):
return self.request.user
After this you just need to set up you UpdateView and create a proper template. You can read about this in the docs.
i'm trying to solve a little issue with my upload file problem in my django 1.5 application. I downloaded a sort of example, but i have some problem with that.
When i press the ulpoad button it says to me that it's empty, but that it's not true...
this is my upload form:
class DocumentForm(forms.Form):
docfile = forms.FileField(
label='Select a file'
this is the view
def image(request, auction_event_id):
# Handle file upload
try:
auction_event = AuctionEvent.objects.get(pk=auction_event_id)
except AuctionEvent.DoesNotExist:
raise Http404
if request.method == 'POST':
form = DocumentForm(request.POST, request.FILES)
if form.is_valid():
newdoc = Document(docfile = request.FILES['docfile'])
newdoc.save()
# Redirect to the document list after POST
return HttpResponseRedirect(reverse('com:auction_view_auction_event', args=(auction_event.pk,)))
else:
form = DocumentForm() # A empty, unbound form
# Load documents for the list page
documents = Document.objects.all()
# Render list page with the documents and the form
return render_to_response(
'auction/setimages.html',
{'documents': documents, 'form': form},
context_instance=RequestContext(request)
)
and the model
class Document(models.Model):
auction_event = models.ForeignKey(AuctionEvent, related_name='images')
docfile = models.FileField(upload_to='documents/%Y/%m/%d')
The usual reason for this is that you've forgotten to add enctype="multipart/form-data" to the HTML form tag. See the docs.