I have used multiple FileFields in a model and my app gives error "The submitted file is empty" on execution, folowing are the details of my code:
My models.py:
class KBCTest(models.Model):
algorithm = models.CharField(max_length=10, blank=False)
entityFile = models.FileField(blank=False,
upload_to=updateFilename,
validators=[validateTestingFileExtension])
relationFile = models.FileField(blank=False,
upload_to=updateFilename,
validators=[validateTestingFileExtension])
My forms.py
class KBCTestForm(forms.ModelForm):
class Meta:
model = KBCTest
fields = ('algorithm', 'entityFile', 'relationFile')
def clean(self):
super(KBCTestForm, self).clean()
data = self.cleaned_data
return data
My views.py:
def testing(request):
title = 'Testing'
template = 'testing.html'
form = ''
if request.method == 'GET':
form = KBCTestForm()
if request.method == 'POST':
form = KBCTestForm(request.POST, request.FILES)
if form.is_valid():
form.save()
runAlgo = Run(form.cleaned_data)
runAlgo.configAndTest()
return HttpResponseRedirect(reverse('learning', kwargs={'function': 'testing'}))
context = {'title': title, 'form': form}
return render(request, template, context)
My template:
{% if request.user.is_authenticated %}
<div class='col-sm-4 col-sm-offset-4'>
<h1>{{ title }}</h1>
<!-- If form not rendered(i.e. views context) don't show submit button -->
{% if form %}
<!-- Use 'csrf_token' to prevent cross-site forgery -->
<form enctype='multipart/form-data' method='POST', action=''>
{% csrf_token %}
{{ form|crispy }} </br>
{{ form.non_field_errors|crispy }}
<input type='submit' value='Test' class='btn btn-default'/>
</form>
{% endif %}
</div>
{% endif %}
When I run this template it fails to validate the form and gives error "The submitted file is empty" error as visible in the screenshot below:
Check out this part of the Django docs, where it mentions the parameter allow_empty_file for the forms.FileField. Maybe it gives you a few clues.
Note: I'm not exactly sure how this applies to ModelForm or Model.FileField.
Related
So I'm making a to-do list and I made a booleanfield modelform which has attribute "complete". I want that user to check it when it's complete and I tried wriring if task.complete == True cross out the item and it didn't work(it only worked when I checked it from the admin panel). Then I tried form.complete instead of task.complete and it doesn't do anything.
models:
class Task(models.Model):
title = models.CharField(max_length=200)
complete = models.BooleanField(default=False)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
forms:
from .models import *
class TaskForm(forms.ModelForm):
title = forms.CharField(widget= forms.TextInput(attrs={'placeholder':'Add new task...'}))
class Meta:
model = Task
fields = '__all__'
html:
<div class="main-container">
<form method="POST" action="/">
{% csrf_token %}
<input type="submit"/>
{{ form.title }}
</form>
{% for task in tasks %}
{{ form.complete }}
{% if form.complete == True %}
<h1><strike>{{task.title}}</strike></h1>
{% else %}
<h1>{{task.title}}</h1>
{% endif %}
{% endfor %}
</div>
views:
def index(request):
tasks = Task.objects.order_by('-created')
form = TaskForm()
if request.method == 'POST':
form = TaskForm(request.POST)
if form.is_valid():
form.save()
return redirect('/')
context = {
'tasks': tasks,
'form': form,
}
return render(request, 'to-do.html', context)
There are some problems with your code I don't know how to explain. Try this. It should work.
<div class="main-container">
<form method="POST" action="/"> # create new task
{% csrf_token %}
{{ form.title }}
<input type="submit" name="new-task"/>
</form>
<form method="post"> # update task status
{% csrf_token %}
{% for task in tasks %}
{% if task.complete %}
<input type="checkbox" name="if_completed" checked value="{{ task.pk }}">
<h1><strike>{{task.title}}</strike></h1>
{% else %}
<input type="checkbox" name="if_completed" value="{{ task.pk }}">
<h1>{{task.title}}</h1>
{% endif %}
{% endfor %}
<input type="submit" name="update-task"/>
</form>
</div>
view.py (Only for form, add your other code with it)
from django.http import HttpResponseRedirect
from django.urls import reverse
def index(request):
tasks = Task.objects.order_by('-created')
form = TaskForm()
if request.method == 'POST':
if 'new-task' in request.POST:
form = TaskForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('home')) # replace home with url name where you want to redirect
elif 'update-task' in request.POST:
task_pk = request.POST.getlist("if_completed")
for i in task_pk:
Task.objects.filter(pk=i).update(complete=True) # I have replace pk with i here
return HttpResponseRedirect(reverse('home')) # replace home with url name where you want to redirect
context = {
'tasks': tasks,
'form': form,
}
return render(request, 'to-do.html', context)
in forms.py
class TaskForm(forms.ModelForm):
class Meta:
model = Task
fields = ('title',)
widgets = {
'title': forms.TextInput(attrs={'placeholder':'Add new task...'})
}
This should work. There may be be some error because of typos or indentation. Let me know if you get any issue
def index(request):
tasks = Task.objects.order_by('-created')
form = TaskForm()
context = {
'tasks': tasks,
'form': form,
}
if request.method == 'POST':
if 'new-task' in request.POST:
form = TaskForm(request.POST)
if form.is_valid():
form.save()
elif 'update-task' in request.POST:
task_pk = request.POST.getlist("if_completed")
for i in task_pk:
Task.objects.filter(pk=pk).update(complete=True)
return render(request, 'to-do.html', context)
Sorry if this is too much code, but I believe it is all relevant to the question at hand.
Long story short, on my series_detail page, all episodes belonging to each series is shown, as well as forms to add a new episode or edit an existing one.
The edit episode form, however, requires an instance, which always returns the very first object of the episodes queryset. This is presumably because of the .first(), but I used this since you can only have one object passed as an instance.
What I am trying to achieve is:
after showing the edit modal next to each episode, show the instance of each episode instead of only the first episode.
save only that episode's instance after the form is filled
achieve this without redirecting to an edit page
models.py
class Series(models.Model):
name = models.CharField(max_length=100)
class Episode(models.Model):
series = models.ForeignKey(Series, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
episode_no = models.IntegerField(null=True)
description = models.TextField(max_length=500)
image = models.ImageField(upload_to='pics/episodes',)
forms.py
class EpisodeForm(forms.ModelForm):
name = forms.CharField()
description = forms.CharField(widget=forms.Textarea, required=False)
episode_no = forms.IntegerField()
class Meta:
model = Episode
fields = ['name', 'description', 'episode_no' ,'image']
views.py
def series_detail(request, pk):
try:
series = Series.objects.get(pk=pk)
except:
return render(request, 'error_404.html')
episodes = Episode.objects.filter(series=series).first()
if request.method == 'POST':
if 'addepisodeform' in request.POST:
e_form = EpisodeForm(request.POST, request.FILES, prefix='addepisode')
e_form.instance.series = Series.objects.get(pk=pk)
if e_form.is_valid():
e_form.save()
return redirect('series_detail', pk=pk)
messages.success(request, 'Episode was created')
else:
return redirect('series_detail', pk=pk)
messages.error(request, 'Episode was not created')
elif 'editepisodeform' in request.POST:
edit_e_form = EpisodeForm(request.POST, request.FILES, instance=episodes, prefix='editepisode')
edit_e_form.instance.series = Series.objects.get(pk=pk)
if edit_e_form.is_valid():
edit_e_form.save()
return redirect('series_detail', pk=pk)
messages.success(request, 'Episode was updated')
else:
return redirect('series_detail', pk=pk)
messages.error(request, 'Episode was not updated')
else:
e_form = EpisodeForm(prefix='addepisode')
edit_e_form = EpisodeForm(instance=episodes, prefix='editepisode')
context = {
'episodes': episodes,
'e_form': e_form,
'edit_e_form': edit_e_form
}
return render(request, 'series/series_detail.html', context)
def delete_episode(request, pk1, pk2):
try:
series = Series.objects.get(pk=pk1)
except:
return render(request, 'error_404.html')
try:
episode = Episode.objects.get(series=series, episode_no=pk2)
except:
return render(request, 'error_404.html')
episode.delete()
return redirect('series_detail', pk=pk1)
urls.py
path('series/<int:pk>/', views.series_detail, name='series_detail'),
path('series/<int:pk1>/episode/<int:pk2>/delete/', views.delete_episode, name='delete_episode'),
series_detail.html
<button type="submit" name="addepisodeform">
Add Episode
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ e_form }}
</form>
</button>
{% for episode in episodes %}
{{ episode.name }}
{{ episode.description}}
<img src="{{ episode.image.url }}" height="125px" width="300px" style="object-fit: cover;">
<button type="submit" name="editepisodeform">
Edit Episode
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ edit_e_form }}
</form>
</button>
{% endfor %}
Okay, so it turns out that formsets were the way to go after all. Thanks to Willem Van Onsem's answer, I decided to go that route after all and it worked like a charm.
A form can only edit one instance, but with formsets, I was able to not only edit each episode rather than just the first instance, but even create a new object and delete multiple objects at the same time!
views.py
#import the formset
from django.forms import modelformset_factory, inlineformset_factory
#formset
EpisodeFormset = inlineformset_factory(Series, Episode, fields=('name', 'episode_no', 'description', 'image'), can_delete=True, extra=1)
#post call
if request.method == 'POST':
if 'editepisodeform' in request.POST:
formset = EpisodeFormset(request.POST, request.FILES, instance=series, prefix='editepisode')
if formset.is_valid():
formset.save()
return redirect('series_detail', pk=pk)
else:
return redirect('series_detail', pk=pk)
else:
formset = EpisodeFormset(instance=series)
series_detail.html
<div>
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
<button type="submit" class="btn btn-primary" name="editepisodeform">Edit</button>
{{ form.as_p }}
{% for episode in episodes %}
{% if episode.episode_no == form.episode_no.value %}
Episode: {{ episode.episode_no }} <br>
Name: {{ episode.name }} <br>
<img src="{{ episode.image.url }}" height="125px" width="300px" style="object-fit: cover;"> <br> {% endif %}
{% endif %}
{% endfor %}
{% endfor %}
</form>
</div>
The page returns the error as_crispy_field got passed an invalid or inexistent field after SUBMIT Button is clicked. I was trying to submit the form when the error is raised. The record is SUCCESSFULLY saved into the database but an error is raised. A form for the template was created to set up the fields. The Form was instantiated in the views so that it could easily map the elements from the FORM to the TEMPLATE.
What caused the error and how can I resolve it?
FORM: Here is my form code
class ModelCreateDescriptionForm(forms.ModelForm):
name = forms.CharField(max_length=100)
description = forms.Textarea()
model_type = forms.ChoiceField(
widget = forms.Select,
choices = MODEL_TYPE_CHOICES,
)
def __init__(self, project_id=1, *args, **kwargs):
super(ModelCreateDescriptionForm, self).__init__(*args, **kwargs)
# project = Project.objects.get(id=project_id)
self.fields['model_type'].choices = MODEL_TYPE_CHOICES
self.fields['model_type'].required = True
class Meta:
model = Model
fields = ['name', 'description', 'model_type']
VIEW: Here is my view
def modelCreationDescription(request, **kwargs):
pk = kwargs.get('pk')
project = Project.objects.get(id=pk)
context = {}
context['project'] = project
if request.method == 'POST':
form = ModelCreateDescriptionForm(pk, request.POST)
name = request.POST.get(u'name')
description = request.POST.get(u'description')
model_type = request.POST.get(u'model_type')
if not(name and model_type):
messages.warning(request, f'Please fill model name and model type!')
if form.is_valid():
formDescription = form.save(commit=False)
try:
formDescription.name = name
formDescription.description = description
formDescription.model_type = model_type
formDescription.project = project
except:
messages.warning(request, f'Something wrong!')
return redirect('all-model-listview')
# save the form value
formDescription.save()
messages.success(request, f'Model description successfully created')
return render(request, 'models/pred_steps/modelSetTargetFeatures.html', {'model_form': formDescription })
else:
form = ModelCreateDescriptionForm(project_id=pk)
context = {
'form': form,
'project': project,
'create_model_description': True,
}
return render(request, 'models/pred_steps/modelCreateDescriptions.html', context)
HTML: This is the template that returning an error
<div class="card border-left-info mb-1 shadow">
<div class="col-xl-10 col-md-8 mb-1">
{% if project %}
<p><h5 class="text-info">Project: {{ project }}</h5></p>
{% endif %}
<form method="POST">
{% csrf_token %}
<fieldset class='form-group'>
{{ form.name|as_crispy_field }}
</fieldset>
<fieldset class='form-group'>
{{ form.description|as_crispy_field }}
</fieldset>
<fieldset class='form-group'>
{{ form.model_type|as_crispy_field }}
</fieldset>
<div class="form-group">
{% if project.id %}
<a class="btn btn-outline-secondary float-right" href="{% url 'all-model-listview' %}">Cancel</a>
{% endif %}
<button class="btn btn-outline-success" type="submit">Submit and Continue</button>
</div>
</form>
</div>
</div>
In case the next template is causing the error here are the codes
FORM:
class ModelSetTargetFeaturesForm(forms.ModelForm):
target_column_classification = forms.ChoiceField(
widget = forms.Select,
)
target_column_regression = forms.ChoiceField(
widget = forms.Select,
)
def __init__(self, project_id=1, *args, **kwargs):
super(ModelSetTargetFeaturesForm, self).__init__(*args, **kwargs)
project = Project.objects.get(id=project_id)
df = pd.read_csv(project.base_file, encoding='ISO-8859-1')
cols = df.columns
a_cols = np.column_stack(([cols, cols]))
self.fields['target_column_classification'].choices = a_cols
self.fields['target_column_classification'].required=True
self.fields['target_column_regression'].choices = a_cols
self.fields['target_column_regression'].required=True
# project = Project.objects.get(id=project_id)
class Meta:
model = Model
fields = ['target_column_classification', 'target_column_regression', ]
VIEW:
def modelSetTargetFeatures(request, **kwargs):
pk = kwargs.get('pk')
model = Model.objects.get(id=pk)
project = Model.objects.get(project=model.project.pk)
context = {}
context['model'] = model
context['project'] = project
if request.method == 'POST':
form = ModelSetTargetFeaturesForm(pk,request.POST)
target_column_classification = request.POST.get(u'target_column_classification')
target_column_regression = request.POST.get(u'target_column_regression')
if not(target_column_regression and target_column_classification):
messages.warning(request, f'Please fill model name and model type!')
if form.is_valid():
formTargetFeatures = form.save(commit=False)
formTargetFeatures.target_column_classification = target_column_classification
formTargetFeatures.target_column_regression = target_column_regression
# save the form value
formTargetFeatures.save()
messages.success(request, f'Model description successfully created')
return render(request, 'models/pred_steps/modelFeaturesSelection.html', {'model_form': formTargetFeatures })
else:
form = ModelSetTargetFeaturesForm(model=pk)
context = {
'form': form,
'project': project,
'model': model,
'create_model_description': True,
}
return render(request, 'models/pred_steps/modelSetTargetFeatures.html', context)
TEMPLATE:
<div class="card border-left-info mb-1 shadow">
<div class="col-xl-10 col-md-8 mb-1">
{% if project %}
<p><h5 class="text-info">Project: {{ project }}</h5></p>
<p><h5 class="text-info">Model: {{ name }}</h5></p>
{% endif %}
<form method="POST">
{% csrf_token %}
<fieldset class='form-group'>
{{ form.target_column_classification|as_crispy_field }}
</fieldset>
<fieldset class='form-group'>
{{ form.target_column_regression|as_crispy_field }}
</fieldset>
<div class="form-group">
{% if project.id %}
<a class="btn btn-outline-secondary float-right" href="{% url 'all-model-listview' %}">Cancel</a>
{% endif %}
<button class="btn btn-outline-success" type="submit">Next: Feature Selection</button>
</div>
</form>
</div>
</div>
Your view is making use of two different html templates:
'models/pred_steps/modelCreateDescriptions.html'
and
'models/pred_steps/modelSetTargetFeatures.html'
The first one is used for presenting the form, upon a GET request, and allowing the participant to input their data: return render(request, 'models/pred_steps/modelCreateDescriptions.html', context)
However, once the participant's data are POST'ed, this happens:
form = ModelCreateDescriptionForm(pk, request.POST)
# ... inside of if_valid
formDescription = form.save(commit=False)
# ...
formDescription.save()
# ...
return render(request, 'models/pred_steps/modelSetTargetFeatures.html', {'form': formDescription })
It's this second rendition that is causing problems, so I assume you are using crispy fields in the modelSetTargetFeatures.html template also. When rendering this other template, you seem to try to include formDescription as a form. However, formDescription is not a form, because form.save(commit=False) returns a Model object, not a form. Hence, django/crispy forms doesn't understand what's going on and rightly says that what you are trying to use as a form is not a valid form (since it's actually a Model instance). If you really do want to pass the form itself on to the other template, you can simply use {'form': form}.
You might also want to use a better name for your model than simply Model. It's very confusing and might cause bugs, since the name is identical to that of django.db.models.Model, which we subclass from for creating specific Models. Additionally, you might want to use the pythonic naming convention of using snakecase (e. g. my_function_based_view) for functions, and camelcase with the first letter capitalized for classes (e. g. MyFormClass).
(note that the above response refers to the code as you originally posted it - you've done some edits since I started responding, but the basic issue seems to be the same even after the edits)
I am working on my first django app. I am building an app that allows the user to rate beer. I want my user to be able to edit an entry they've already created. I take them to a ModelForm, and ask for their entry. When the POST method is called, my data is invalid. Here is my model.py:
from django.db import models
class Rating(models.Model):
beer_name = models.TextField()
score = models.DecimalField(max_digits=2, decimal_places=1)
notes = models.TextField(blank=True)
brewer = models.TextField(blank=True)
and forms.py:
from django import forms
from ratings.models import Rating
class RatingForm(forms.ModelForm):
class Meta:
model = Rating
fields = ['beer_name', 'score', 'notes', 'brewer']
Here is the views.py of my edit function:
def edit(request, row_id):
rating = get_object_or_404(Rating, pk=row_id)
if request.method == "POST":
form = RatingForm(request.POST, instance=rating)
if form.is_valid():
form.save()
return redirect(home)
else:
return HttpResponse("Invalid entry.")
else:
context = {'form': rating}
form = RatingForm(instance=rating)
return render(
request,
'ratings/entry_def.html',
context
)
However, every time the POST is called I get an "Invalid entry." HttpResponse, meaning my form.is_valid() is being returned False. Here is my template:
{% extends "base.html" %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-sm-10 col-sm-offset-1">
<h2>Edit Rating</h2>
<form role="form" method="post">
{% csrf_token %}
<p>Beer Name: <textarea>{{ form.beer_name }}</textarea></p>
<p>Score: <input type="text" name="BeerScore" value="{{ form.score }}"></p>
<p>Notes: <textarea>{{ form.notes }}</textarea></p>
<p>Brewer: <textarea>{{ form.brewer }}</textarea></p>
<p><button type="submit" class="save btn btn-primary">Save</button></p>
<p><button type="reset" class="btn btn-primary">Cancel</button></p>
</form>
</div>
</div>
</div>
{% endblock %}
So when I press my Save button, I am getting the response. Here is my edit url in urls.py:
urlpatterns = [
...
url(r'rating/edit/(?P<row_id>[0-9]+)/$', edit , name='rating-edit'),
]
You're wrapping fields in other fields which don't have name attributes. This is most likely causing the values to be excluded from the request.POST data.
Additionally, Django form fields all have a corresponding HTML widget. So there's really no need to render the HTML by hand, unless you need to.
Change your template code to:
<p>
{{ form.beer_name.label }}: {{ form.beer_name }}
{% if form.beer_name.errors %}
<br />{{ form.beer_name.errors }}
{% endif %}{# repeat for other fields as needed #}
</p>
<p>{{ form.score.label }}: {{ form.score }}</p>
<p>{{ form.notes.label }}: {{ form.notes }}</p>
<p>{{ form.brewer.label }}: {{ form.brewer }}</p>
<p><button type="submit" class="save btn btn-primary">Save</button></p>
<p><button type="reset" class="btn btn-primary">Cancel</button></p>
If you need to change the widget, do so at the form class level:
class RatingForm(forms.ModelForm):
class Meta:
model = Rating
def __init__(self, *args, **kwargs):
super(RatingForm, self).__init__(*args, **kwargs)
self.fields['notes'].widget = forms.Textarea()
This way, Django manages the attributes and binding for you.
Your view can also use some cleanup:
def edit(request, row_id):
rating = get_object_or_404(Rating, pk=row_id)
form = RatingForm(request.POST or None, instance=rating)
if request.method == "POST" and form.is_valid():
form.save()
return redirect(home)
context = {'form': rating}
return render(request, 'ratings/entry_def.html', context)
I've made a profile model in models.py:
class Profile(models.Model):
user = models.OneToOneField(User)
title = models.CharField(max_length=20, default='title')
firstname = models.CharField(max_length=40, default='firstname')
lastname = models.CharField(max_length=40, default='lastname')
blurb = models.CharField(max_length=500, default='tell us about yourself')
#work out how to make filename equal the username
pic = models.ImageField(default="static/profile/blank.png", upload_to='static/profile/%d%m%y.jpg') #+ user.pk + '.jpg')
def __unicode__(self):
return self.user.username
and here is the view for a page to edit the profile of a logged in user:
def editprofile(request):
u_p = request.user.profile
template = loader.get_template('registration/editprofile.html')
if request.method == 'POST':
form = ProfileForm(request.POST, instance=u_p)
if form.is_valid():
form.save()
else:
# todo
None
else:
#todo
context = RequestContext(request, {'form': form})
return HttpResponse(template.render(context))
The template fragment reads:
<form method="POST" action=".">
{% csrf_token %}
<div class="regformout">
<div class="regform">
{% for field in form %}
<div class='cell'> {{ field.label_tag }} </div>
<div class='nogin'> {{ field.errors }} </div>
<div class='cell'> {{ field }} </div>
{% endfor %}
</div>
</div>
<input class="btn btn-large btn-primary" type="submit" value="Save Your Profile" ></input>
</form>
I want the form fields to automatically populate with the data for the current user on the corresponding page for editing the profile. However, no matter what I try I cannot make this happen. What am I doing wrong here?
Your main problem is you are only populating the form if the user hits the submit button, so when the view is requested initially, your form is empty.
from django.shortcuts import render, redirect
def editprofile(request):
u_p = request.user.profile
form = ProfileForm(request.POST or None, instance=u_p)
if request.method == 'POST':
if form.is_valid():
form.save()
return redirect('/')
return render(request,
'registration/editprofile.html', {'form': form})