I am trying to upload files (single and multiple) with an UpdateView via an update template but I keep getting a 'This field is required' error when I submit, and the files aren't uploaded, of course. All required fields are filled with valid data types and the file upload fields are optional, so I don't understand where this error is coming from.
I strongly suspect there's an error in my view specifically in the if form.is_valid(): section. I tried using both function-based (FBV) and class-based (CBV) views, still getting the same error. I have even created a separate model for the multiple files upload field and a separate form class that extends the UpdateForm class, and I'm still getting the same error.
This is the model for the multiple files upload field:
class shareCapitalFiles(models.Model):
# Foreign key
coopform = models.ForeignKey(CoopForm, on_delete=models.CASCADE)
# Attach evidence of share capital
attach_share_capital = models.FileField('Attach Share Capital', upload_to="documents", blank=True)
This is my forms.py:
# Update Form
class updateCoopForm(forms.ModelForm):
nature_registration = forms.ChoiceField(widget=forms.RadioSelect(attrs={'class': 'flex inline-flex', 'cursor': 'pointer'}), choices=CoopForm.REGISTRATION_TYPE)
have_bye_laws = forms.ChoiceField(widget=forms.RadioSelect(attrs={'class': 'flex inline-flex', 'cursor': 'pointer'}), choices=CoopForm.BYE_LAWS_CHOICES)
attach_bye_laws = forms.FileField(required=False)
class Meta:
model = CoopForm
fields = '__all__'
widgets = {
'first_purpose': forms.Textarea(attrs={'rows':2}),
'second_purpose': forms.Textarea(attrs={'rows':2}),
'third_purpose': forms.Textarea(attrs={'rows':2}),
'first_origin_meeting_date': forms.DateInput(attrs={'type': 'date'}),
'second_origin_meeting_date': forms.DateInput(attrs={'type': 'date'})
}
class shareCapitalForm(updateCoopForm):
attach_share_capital = forms.FileField(label='Attach Share Capital',widget=forms.ClearableFileInput(attrs={'multiple': True}), required=False)
class Meta(updateCoopForm.Meta):
fields = updateCoopForm.Meta.fields
this is my CBV
class updateFormView(UpdateView):
model = CoopForm
form_class = shareCapitalForm
context_object_name = 'obj'
template_name = 'updateform.html'
def get_queryset(self):
return CoopForm.objects.all()
def get_success_url(self):
return reverse('formdetails', kwargs={'pk': self.object.id})
def get_context_data(self, **kwargs):
context = super(updateFormView, self).get_context_data(**kwargs)
if self.request.POST:
context['form'] = shareCapitalForm(self.request.POST, self.request.FILES, nstance=self.object)
else:
context['form'] = shareCapitalForm(instance=self.object)
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form_class = self.get_form_class()
form = self.get_form(form_class)
files = self.request.FILES.getlist('attach_share_capital')
if form.is_valid():
update = form.save(commit=False)
if files:
for f in files:
instance = shareCapitalFiles.objects.create(coopform=update, attach_share_capital=f)
instance.save()
messages.success(request, 'Files uploaded successfully')
#update.author = self.request.user
update.save()
return self.form_valid(form)
else:
return self.form_invalid(form)
this is my FBV
def updateformview(request, pk, *args, **kwargs):
# fetch the object related to passed id
obj = get_object_or_404(CoopForm, id=pk)
# pass the object as instance in form
form = shareCapitalForm(request.POST or None, request.FILES or None, instance=obj)
# get the template
template = loader.get_template('updateform.html')
# if the form is being updated
if request.method == 'POST':
# retrieve multiples files from model field
files = request.FILES.getlist('attach_share_capital')
# save the data from the form and
# redirect to detail_view
if form.is_valid():
update = form.save(commit=False)
update.save()
if files:
for f in files:
instance = shareCapitalFiles.objects.create(coopform=update, attach_share_capital=f)
instance.save()
return HttpResponseRedirect(reverse('formdetails', kwargs={'pk': id}))
context = {
'form': form,
'obj': obj
}
return HttpResponse(template.render(context, request))
this is the html code for the multiple files upload field
<div class="grid gap-6 mt-1 mx-auto md:grid-cols-2 lg:grid-cols-2 px-6 pt-4 pb-8 bg-stone-50 border border-1 border-gray-300 shadow-md">
<!-- Attach Share Capital -->
<div>
<label for="attach_share_capital" class="block mb-2 text-md font-bold text-gray-500">
{{ form.attach_share_capital.label }}
</label>
{% render_field form.attach_share_capital class+="cursor-pointer text-md md:text-md font-medium block rounded-sm w-full p-2 border border-2 border-gray-300 placeholder-gray-500 text-gray-600 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" %}
<p class="mt-1 text-sm text-gray-500">PDF, DOC (WORD), PNG OR JPG formats only. You can attach multiple files</p>
</div>
{% if form.errors.attach_share_capital %}
<span class="text-red-600 font-medium">{{ form.errors.attach_share_capital }}</span>
{% endif %}
</div>
I was able to figure out the problem. It was the ID field in my models.py. It was set to models.IntegerField instead of models.AutoField or models.BigAutoField, so it was expecting an input from the user instead of django automatically filling and incrementing it
# what caused the error
id = models.IntegerField(primary_key=True)
# solution
id = models.BigAutoField(primary_key=True)
Related
Currently, I am logged in as an owner and I want to update the fields of customers in the database. But the form does not update or show the details as a placeholder because the user model has extended the customer model and hence, the customer model does not have its own fields. How do I get the instance of the User of the Customer in the UpdateView?/How do I update the customer?
urls.py
urlpatterns = [
path('<int:pk>/update/',CustomerUpdateView.as_view()),
]
views.py
class CustomerUpdateView(OwnerAndLoginRequiredMixin, generic.UpdateView):
template_name = "customer_update.html"
form_class = CustomerModelForm
queryset = Customer.objects.all()
def get_success_url(self):
return "/customers"
models.py
class Customer(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
def __str__(self):
return self.user.username
class User(AbstractUser):
is_owner = models.BooleanField(default=True)
is_agent = models.BooleanField(default=False)
is_customer = models.BooleanField(default=False)
forms.py
class CustomerModelForm(forms.ModelForm):
class Meta:
model = User
fields = (
'email',
'username',
'first_name',
'last_name',
)
So at this point, I'd have to assume a few things here... Let's say you have a ListView to render a list of customers.
views.py file:
class CustomerListView(OwnerAndLoginRequiredMixin, generic.ListView):
template_name = "customer_update.html"
queryset = Customer.objects.all()
context_object_name = 'customers'
urls.py file:
urlpatterns = [
...
path('customers/', CustomerListView.as_view(), name='customers'),
path('update-customer/<int:pk>/', CustomerUpdateView.as_view(), name='update-customer'),
# You can pass whatever customer related info you wish via url, I'm just using id as a simply demo.
...
]
html file:
{% for customer in customers %}
# Displaying other related info per customer
# Link to click to update a particular customer profile: passing the customer pk via url
Update {{ customer.user.username|title }} Profile
{% endfor %}
Back to the views.py file:
class CustomerUpdateView(OwnerAndLoginRequiredMixin, generic.UpdateView):
template_name = "customer_update.html"
form_class = CustomerModelForm
# context_object_name = 'customer'
# queryset = Customer.objects.all() # not needed here
# You can use the get_object() on the class to grab the customer object by the pk passed via url
def get_object(self, queryset=None):
customer_pk = self.kwargs.get('pk', None)
return get_object_or_404(Customer, pk=customer_pk)
# Also, you could use the get_context_data() to set the form values before rendering on the page
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
customer = self.get_object() # -> returns a customer object
# creating a dictionary to use to initialize the form: accessing the user information on the customer
data = {
'username': customer.user.username,
'first_name': customer.user.first_name,
'last_name': customer.user.last_name,
'email': customer.user.email,
}
form = self.form_class(initial=data, instance=customer.user) # updated here too
context['form'] = form # -> updating the class view context dictionary
return context
def get_success_url(self):
return "/customers"
Now within the customer_update.html:
<form method="POST">
<div>
{{ form.username }}
{{ form.email }}
{{ form.first_name }}
{{ form.last_name }}
</div>
<input type="submit" value="Update"/>
</form>
Ideally, that should display the customer's information in the form.
UPDATES
To handle and save the form submission, you can use the post() on the update-view. You can add to the CustomerUpdateView:
# Use the post method to handle the form submission
def post(self, request, *arg, **kwargs):
# Should set the user instance on the form
customer = self.get_object()
form = self.form_class(request.POST, instance=customer.user) # updated here too
if form.is_valid():
form.save()
return redirect('to any path of your choice') # Redirect upon submission if necessary
else:
print(form.errors) # To see the field(s) preventing the form from being submitted
# Passing back the form to the template
return render(request, self.template_name, {'form': form})
Im trying to add a field called, interested_fields inside my personalInfo model which users can choose from and the choices themselves come from another models' objects with the help of ManyToMany relation between the two models. Here are my models.py codes(I simplified my personal model by removing some other fields like name, age, etc in order to make it more readable for you):
class Field(models.Model):
id = models.AutoField(primary_key=True)
slug = models.CharField(max_length=16, default='default')
title = CharField(max_length=32)
class PersonalInfo(models.Model):
id = models.AutoField(primary_key=True)
interested_fields = models.ManyToManyField(Field, blank=True)
then, I created a ModelForm like this:
class InterestedFieldsForm(forms.ModelForm):
interested_fields = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, choices=Field.objects.all(), required=False)
class Meta:
model = PersonalInfo
fields = ['interested_fields']
and created a get and post functions inside my views like this:
class PersonalView(View):
template_name = 'reg/personal.html'
def get(self, request, *args, **kwargs):
context = {}
context['fields'] = Field.objects.all()
return render(request, self.template_name, context=context)
def post(self, request, *args, **kwargs):
user = request.user
if request.method == 'POST':
form = InterestedFieldsForm(request.POST)
if form.is_valid():
profile = form.save(commit=False)
profile.user = request.user
profile.save()
else:
form = InterestedFieldsForm()
return render(request, 'reg/done.html', context={'form': form})
and finally in template, inside the form I added this for loop:
{% for field in fields %}
<label class="containerq ant-col ant-col-md-6 ant-col-xs-8" >
<span>
<input type="checkbox" name="interested_fields" {% if field.slug in user.personalInfo.interested_fields %} checked="checked" {% endif %} value="{{field.title}}">
<span style="margin-left:7px" class="checkmark"></span>
</span>
<span>{{field.title}}</span>
</label>
{% endfor %}
when I submit the form it gives me this error:
cannot unpack non-iterable Field object
Im new to django so I really dont know what am I doing wrong. thank you for your answers
You should use a ModelMultipleChoiceField
interested_fields = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, queryset=Field.objects.all(), required=False).
I am trying to create a form to submit a blog post on an author detail page, so that the blog post will automatically use the current author as its "blog_author" foreign key. I'm aware that this approach isn't "secure" - it's a project site, and I'm trying to learn a new design pattern.
The Django docs recommended using 1 parent view and 2 subviews to handle get and post respectively (https://docs.djangoproject.com/en/3.0/topics/class-based-views/mixins/).
The page renders fine with the get, but the post gives me an error reading "Page not found (404) - no blog post found matching the query." The exception is raised by my parent view (blog.views.AuthorDetail), but there is no traceback.
Edit: Form should have been a ModelForm from the beginning
Here are my views:
class BlogAuthorDetailView(generic.DetailView):
model = BlogAuthor
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = BlogSubmitForm()
return context
class BlogSubmit(SingleObjectMixin, FormView):
template_name = 'blogauthor_detail.html'
form_class = BlogSubmitForm
model = BlogPost
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
#Should I be overriding form_valid() to use the line above? Not sure if I'm doing my data
#handling in the right place
return super().post(request, *args, **kwargs)
def form_valid(self, form):
blogpost = form.save(commit=False)
blogpost.blog_author = self.object
blogpost.save()
return redirect('blog_author-detail', pk=self.object.id)
class AuthorDetail(View):
def get(self, request, *args, **kwargs):
view = BlogAuthorDetailView.as_view()
return view(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
view = BlogSubmit.as_view()
return view(request, *args, **kwargs)
URLs:
urlpatterns = [
path('', views.index, name='index'),
path('blogs/', views.BlogPostListView.as_view(), name='blogs'),
path('blog/<int:pk>', views.BlogPostDetailView.as_view(), name='blogpost-detail'),
path('bloggers/', views.BlogAuthorListView.as_view(), name='bloggers'),
path('blogger/<int:pk>', views.AuthorDetail.as_view(), name='blog_author-detail'),
path('blog/<int:pk>/create', views.BlogCommentCreate.as_view(), name='comment_create')
]
the template:
{% extends "base_generic.html" %}
{% block content %}
<h1>Title: {{ blogauthor.title }}</h1>
<p><strong>Author:</strong> {{ blogauthor }}</p>
<p><strong>Biography:</strong> {{ blogauthor.biography }}</p>
<p><strong>User:</strong> {{ blogauthor.user }}</p>
<p><strong>Posts:</strong>
{% for blog in blogauthor.blogpost_set.all %}
<p> {{ blog.title }} </p>
{% endfor %} </p>
<form action="" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Submit">
</form>
<div style="margin-left:20px;margin-top:20px">
<h4>Comments: Coming Soon!</h4>
{% endblock %}
Model:
class BlogPost(models.Model):
date_created = models.DateField(blank=False, default = date.today)
blog_author = models.ForeignKey('BlogAuthor', on_delete = models.SET_NULL, null=True)
title = models.TextField(max_length=70)
content = models.TextField(max_length=400, null=False)
class Meta:
ordering = ['date_created']
def get_absolute_url(self):
"""Returns the url to access a particular blog post instance."""
return reverse('blogpost-detail', args=[str(self.id)])
def __str__(self):
return self.title
And the forms.py:
class BlogSubmitForm(forms.Form):
title = forms.CharField()
content = forms.CharField(widget=forms.Textarea(attrs={'cols': 40, 'rows': 8}))
date_created = forms.DateField()
At this point, I suspect that the problem is related to my redirect() call in the form_valid override.
The things I have tried include:
Changing the form’s action from blank to the same URL as in my URL paths (possible I did this wrong)
Changing the code in form_valid() to read form.instance.blog_author = self.object (same exact error message, so I don’t think it’s this)
Fiddling with the form_valid()’s redirect call, including: using self.object instead or a URL, using a hardcoded url, getting rid of the second argument, and changing the 2nd arg to pk=, slug=.
Adding a get_success_url override (don’t really know why this would work)
edit: one of the excepted post calls that showed up in my local server went to blog/blogger/4, which is the url I want. Not sure what the issue is.
This is confusing on how you are using the template. Anyway, I think the simplest solution here is to get the BlogAuthor data from request.user and that is most logical, otherwise, anyone can post anything from another user as long as they can predict their primary key(which is a security hole). Here is how you can try:
from django.contrib.auth.mixins import LoginRequiredMixin
class BlogSubmit(LoginRequiredMixin, CreateView):
template_name = 'blogauthor_detail.html'
form_class = BlogSubmitForm
model = BlogPost
def get_success_url(self):
return reverse('blog_author-detail', pk=self.object.id)
def form_valid(self, form):
form.blog_author = self.request.user.blogauthor # assuming BlogAuthor has OneToOne relation with User
return super(BlogSubmit, self).form_valid(form)
Update
Purpose of FormView is to collect data from Forms, where CreateView is to store and create a new instance. Anyway, you need to change your code like this to make it work:
class BlogSubmit(LoginRequiredMixin, SingleObjectMixin, FormView):
template_name = 'blogauthor_detail.html'
form_class = BlogSubmitForm
model = BlogAuthor
def get_success_url(self):
return reverse('blog_author-detail', pk=self.object.id)
def form_valid(self, form):
self.object = self.get_object()
form.blog_author = self.object
form.save()
return super(BlogSubmit, self).form_valid(form)
Also update the form:
class BlogSubmitForm(forms.ModelForm):
class Meta:
model = BlogPost
fields = ['title', 'date_created', 'content']
FYI, to make SingleObjectMixin work, you need to change the model from BlogPost to BlogAuthor
i've created web blog with django 2.2 each post has multiple images , but when i try to update the post the images wont updated
i use class based view
class Post(models.Model):
user= models.ForeignKey(Account,on_delete=models.CASCADE)
title= models.CharField(max_length=100)
#others
class PostImage(models.Model):
post= models.ForeignKey(Post,on_delete=models.CASCADE,related_name='images')
media_files = models.FileField(upload_to=random_url)
and this my forms.py
class PostImageForm(forms.ModelForm):
class Meta:
model = PostImage
fields = [
'media_files'
]
class PostUpdateForm(forms.ModelForm):
class Meta:
model = Post
fields = [
'title','description',#and others
]
my views.py
PostImageFormSet = inlineformset_factory(
Post,PostImage,form=PostImageForm,extra=1,can_delete=True,can_order=False
)
class PostUpdateView(LoginRequiredMixin,UserPassesTestMixin,UpdateView):
model = Post
form_class = PostUpdateForm
template_name = 'posts/update_post.html'
def get_context_data(self,**kwargs):
data = super().get_context_data(**kwargs)
if self.request.POST:
data['images'] = PostImageFormSet(self.request.POST or None,self.request.FILES,instance=self.object)
else:
data['images'] = PostImageFormSet(instance=self.object)
return data
def form_valid(self,form):
context = self.get_context_data()
images = context['images']
with transaction.atomic():
if form.is_valid() and images.is_valid():
self.object = form.save()
images.instance = self.object
images.save()
return super().form_valid(form)
def test_func(self):
post = self.get_object()
if self.request.user.username == post.user.username:
return True
return False
def get_success_url(self):
return reverse_lazy('post:post-detail',kwargs={'slug':self.object.slug})
my templates
<form enctype="multipart/form-data" method="post" action="">
{% csrf_token %}
{{images.management_form }}
{{ form|crispy }}
{% for img in images %}
<label>{{img.media_files.label}}</label>
{{img.media_files}}
{% endfor %}
<button type="submit" class="btn btn-block btn-primary">update</button>
</form>
it only save the post form not images , it doesnt affect images form
thanks
I think instead of writing:
images.instance = self.object
Please try this:
I noticed you are using the form is valid so it might be slightly different, but if you change it a bit it should work.
form = PostUpdateForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
images = cd.get('images')
Or using your current setup(not tested yet)
if form.is_valid() //Remove this part -> and images.is_valid():
if form.is_valid():
self.object = form.save()
instance = self.object
cd = form.cleaned_data
images = cd.get('images')
images.save()
I have been trying to code a Multi Upload for Images, my code only
uploads 1 image even though more than 1 is selected, I don´t know how to iterate through, I did a print once the files were selected and my multiple images selected are printed, but when I save the form it only saves one image.
I basically trying to use the code that appear in the Django
documentation.
models.py
class Images(models.Model):
picture = models.ImageField(upload_to='media/photoadmin/pictures')
forms.py
class UploadImages(forms.ModelForm):
class Meta:
model = Images
fields = ('picture',)
widgets = {'picture': forms.ClearableFileInput(
attrs={'multiple': True})}
views.py
class Upload(FormView):
form_class = UploadImages
template_name = 'photoadmin/upload.html'
success_url = 'photoadmin/'
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
files = request.FILES.getlist('picture')
if form.is_valid():
form.save()
for f in files:
file_instance = Images(picture=f)
file_instance.save()
return render(request, 'photoadmin/index.html')
else:
return render(request, 'photoadmin/index.html')
html
{% extends 'base.html' %}
{% block content %}
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Upload</button>
</form>
<p>Return to home</p>
{% endblock %}
This code write in the DB but do not uploads the file to the static
folder
You might need to save every file individually, using FileSystemStorage
from django.core.files.storage import FileSystemStorage
...
class Upload(FormView):
form_class = UploadImages
template_name = 'photoadmin/upload.html'
success_url = 'photoadmin/'
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
files = request.FILES.getlist('picture')
if form.is_valid():
fs = FileSystemStorage()
for file in files:
fs.save(file.name, file)
return render(request, 'photoadmin/index.html')
else:
return self.form_invalid(form)
So, this is finally the solution.
views.py
class UploadView(generic.CreateView):
form_class = UploadImages
model = PostSession
template_name = 'photoadmin/upload.html'
success_url = reverse_lazy('upload')
def form_valid(self, form):
object = form.save(commit=False)
form.save()
if self.request.FILES:
for afile in self.request.FILES.getlist('picture'):
img = object.images.create(picture=afile)
return super(UploadView, self).form_valid(form)
with that you are able to iterate
models.py
class PostSession(models.Model):
session_name = models.CharField(max_length=25)
def __str__(self):
return str(self.session_name)
class Images(models.Model):
name = models.ForeignKey(
PostSession, related_name='images', on_delete=models.CASCADE, null=True, blank=True)
picture = models.ImageField(upload_to='pictures')
Hope this helps the community!