Use view/form in Django to upload a multiple files - python

Class below is used to create a form in Django and get neccesary information. Problem is, it offers only one file to upload. I need to upload multiple files. I use Crispy forms.
My simplified view.py looks like:
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'file_1']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
HTML code:
{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="content-section">
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Fill form</legend>
{{ form|crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Submit</button>
</div>
</form>
</div>
{% endblock content %}
When I inspect page, object looks like:
<input type="file" name="file_1" class="clearablefileinput form-control-file" id="id_file_1">
I want it to contain multiple atribute. How can I achive that? I can't get it to work using documentation (https://docs.djangoproject.com/en/3.2/topics/http/file-uploads/).
I have tried:
widgets = {'file_1': form.ClearableFileInput(attrs={'multiple': True})}
form.instance.file_1 = form.FileField(widget=form.ClearableFileInput(attrs={'multiple':True}))
form.instance.file_1 = form.FileField(widget=form.FileInput(attrs={'multiple': True}))
My models.py
file_1 = models.FileField(blank=True, upload_to='PN_files/%Y/%m/%d/', verbose_name="File 1", validators=[validate_file_size], help_text="Allowed size is 50MB")
I can't find an example how to implement multiple files upload which could be implemented in my class.
UPDATE! (thanks to 'amadou sow')
I've updated my class to:
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'file_1']
def form_valid(self, form):
form.instance.author = self.request.user
obj = form.save(commit=False)
if self.request.FILES:
for f in self.request.FILES.getlist('file_1'):
obj = self.model.objects.create(file=f)
return super().form_valid(form)
And I've added script to my HTML page to add atribute of MULTIPLE:
<script>
$(document).ready(function(){
$('#id_file_1').attr("multiple","true");
})
</script>
Now I get an option to select multiple files and upload them, but when I do that, only 1 file is stored in my media folder.

To answer your question i will try to create a foreign key to the post and i will use function views
app/models.py:
class Post(models.Model):
#add your all other fields here execept (files)
class File(models.Model):
post = models.ForeignKey(Post,on_delete=models.CASCADE)
file = models.FileField(blank=True, upload_to='PN_files/%Y/%m/%d/', verbose_name="Files", validators=[validate_file_size], help_text="Allowed size is 50MB")
app/forms.py:
from .models import Post,File
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = [#the field that you want to rendered]
class PostFileForm(PostForm): #extending form
file = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}))
class Meta(PostForm.Meta):
fields = PostForm.Meta.fields + ['file',]
app/views.py:
from .forms import PostFileForm
from .models import File
def postcreate(request):
if request.method == 'POST':
form=PostFileForm(request.POST or None,request.FILES or None)
files = request.FILES.getlist('file')
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
#add everything you want to add here
post.save()
if files:#check if user has uploaded some files
for f in files:
File.objects.create(post=post,file=f)
return redirect('the-name-of-the-view')
else:
form = PostFileForm()
return render(request,'the-template-you-want-to-rendered-',{'form':form})
with this you can upload many files you want.and if you don't want the file to be required you can add "required=False" inside the PostFileForm.

Related

Django CreateView doesn't save object and doesn't give error

I'm working on a new project and I'm having difficulties adding new objects from the users page. It can be added from admin dashboard.
This is the model:
class Product(models.Model):
title = models.CharField(max_length=150)
price = models.IntegerField()
image = models.ImageField(upload_to='products')
description = models.TextField(max_length=500)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('product-details', kwargs={'pk': self.pk})
I have this view:
class ProductCreateView(LoginRequiredMixin, CreateView):
model = Product
fields = ['title', 'image', 'description', 'price']
def form_valid(self, form):
form.instance.owner = self.request.user
#form.save() # tried this too and it didn't work
return super().form_valid(form)
product_form.html:
{% extends "index/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="content-section">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Product</legend>
{{ form|crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Save</button>
</div>
</form>
</div>
{% endblock content%}
I tried a couple of times and I didn't work. So I searched for solutions and tried the following:
instance = form.save(commit=False)
instance.owner = self.request.user
instance.save()
return super().form_valid(instance)
and this
self.object.owner = self.request.user
self.object = form.save()
return super(ProductCreateView, self).form_valid(form)
within the form_valid(). Neither of them worked. So I can open the form and fill the fields. When I send it, the object is not saved but it doesn't give any error. It just reloads the form.
Ok, so after nearly one week of trying to fix the issue and not much help, I found a solution! form_valid() works fine the way it is, but the problem is in the form. Adding enctype fixes the problem:
<form method="POST" enctype="multipart/form-data">
The explanation I found for this is that without enctype the image data is not being passed correctly to the database. So it looks like the form is fine, but on the background it is not saving the image.

Which one is more convenient to create django form, CreateView or forms.ModelForm

I am very beginner in django. I want to create a post form which be able to have title, content, image/file upload and etc.
I am very confused with concept of modelforms and createview. I tried this:
blog/view.py:
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'content', 'imagefile']
success_url = '/blog/home/'
# template_name = 'blog/post_form.html'
def __init__(self, *args, **kwargs):
super(PostCreateView, self).__init__(**kwargs) # Call to ModelForm constructor
def form_valid(self, form):
form.instance.author = self.request.user
form.save()
return super().form_valid(form)
blog/templates/blog/post_form.html:
{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="content-section">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Post::</legend>
{{ form|crispy }}
<img src='{{ post.imagefile.url }}'>
<br><br>
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Update</button>
</div>
</form>
</div>
{% endblock content %}
blog/urls.py:
from django.urls import path
from .views import (
PostCreateView,
)
urlpatterns = [
path('blog/post/new/', PostCreateView.as_view(), name='post-create')
]
blog/models.py
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
# image = models.ImageField(default='default_post.jpg', upload_to='postimages')
imagefile = models.FileField(upload_to='postimages', null=True, verbose_name="")
# if user is deleted the idea should be deleted as
author = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
# return self.title
return self.title + ": " + str(self.imagefile)
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.pk})
My question: All I want is make a kind of form to create the new post with title, content, upload button and submit button. However I don't know if CreateView can be customised even for adding further items or forms.Modelforms should be used?
You would want your view to be in views.py and your form to be in forms.py. You will need both, not one or the other.
Something like:
class CreateXYZView(CreateView):
template_name = "blog/post_form.html"
model = Post
form_class = postform
...do view stuff
def post(self, request, *args, **kwargs):
Also have a forms.py file
class postform(forms.ModelForm):
class Meta:
model = Post
widgets = {}
exclude = ['fieldname']
It looks like you are using a model named Post which is a good idea. By declaring postform as a forms.ModelForm it will pull your model fields into the form I.E from class Post it pulls title, content, imagefield, etc.. unless the field is specifically added to the exclude parameter. This is useful for parameters in your model like auto fields created_by or post_date where the user should not fill these in manually. Add these to exclude so they do not show in the form.
You can also manually add form fields in your template after {{form|crispy}} but I would avoid that as it creates more work in processing the data.
If you are filling out a form that is not tied to a model you can also use forms.Form:
class SupportTicket(forms.Form):
title = forms.CharField(label="Titlte", max_length=250, widget=forms.TextInput(...)
content = forms....
E.G. use this where the information was being passed directly to GitLab and not saved locally into a model for use later.

Upload multi img to 1 post on Django ? How do that?

I want to upload multi img to 1 post on Django. I used ImageField in models.py. But it's just can upload 1 image to 1 post. How can i upload multi image to 1 post with Django or someways to solved that problem. Thank you so much.
Sorry for the past answer.
I think this is the best way.
models.py
from django.db import models
from django.template.defaultfilters import slugify
class Post(models.Model):
title = models.CharField(max_length=128)
body = models.CharField(max_length=400)
def get_image_filename(instance, filename):
title = instance.post.title
slug = slugify(title)
return "post_images/%s-%s" % (slug, filename)
class Files(models.Model):
post = models.ForeignKey(Post, default=None, on_delete=models.CASCADE)
files = models.FileField(upload_to=get_image_filename, verbose_name='File')
I did not create a form for the files because we will write it manually in the template.
forms.py
from django import forms
from .models import Post, Images
class PostForm(forms.ModelForm):
title = forms.CharField(max_length=128)
body = forms.CharField(max_length=245, label="Item Description.")
class Meta:
model = Post
fields = ('title', 'body', )
views.py
from django.shortcuts import render
from django.contrib import messages
from django.http import HttpResponseRedirect
from .forms import PostForm
from .models import Post, Files
def post(request):
if request.method == 'POST':
print(request.FILES.getlist('files'))
postForm = PostForm(request.POST)
if postForm.is_valid():
post_form = postForm.save(commit=False)
post_form.save()
if request.FILES.getlist('files'):
for file in request.FILES.getlist('files'):
obj = Files(post=post_form, files=file)
obj.save()
messages.success(request, "Yeeew, check it out on the home page!")
return HttpResponseRedirect("/")
else:
print(postForm.errors)
else:
postForm = PostForm()
return render(request, 'index.html', {'postForm' : postForm})
index.html
<html>
<form id="post_form" method="post" action="" enctype="multipart/form-data">
{% csrf_token %}
{% for hidden in postForm.hidden_fields %}
{{ hidden }}
{% endfor %}
{% for field in postForm %}
{{ field }} <br />
{% endfor %}
<input type="file" id="files" name="files" multiple><br>
<input type="submit" name="submit" value="Submit" />
</form>
</html>
We make such a field for the model of files that we do not form.
<input type="file" id="files" name="files" multiple><br>
You can select multiple files and upload them by holding down CTRL or Shift.
i think you are asking inline models,
https://docs.djangoproject.com/en/2.2/ref/contrib/admin/#inlinemodeladmin-objects

Django adding to database with foreign key while still showing the informations from the other model

I want to add elements to my database (for model Student) while having stuff from another model (School) be displayed alongside the form for the Student.\
This is the models.py
class School(models.Model):
name = models.CharField(max_length=256)
principal = models.CharField(max_length=256)
location = models.CharField(max_length=256)
def get_absolute_url(self):
return reverse('basiccbv:detail', kwargs={'pk':self.pk})
def __str__(self):
return self.name
class Student(models.Model):
name = models.CharField(max_length=256)
age = models.PositiveIntegerField(validators= [validators.MinValueValidator(1),validators.MaxValueValidator(20)],default=1)
school = models.ForeignKey(School, related_name='students')
def __str__(self):
return self.name
In my views.py I have this:
class SchoolDetailedView(DetailView):
context_object_name = 'school_detail'
model = models.School
template_name = 'basiccbv/school_detail.html'
# What i want is when I visit the link in the description I want to
# to see the school stuff and the form to add the student in this new
# view
class StudentCreateView(CreateView):
model = models.School
# I tried using the Student but that I don't know how to display the
# school information, I tried with related_name = 'students' but it
# didn't work(I don't know if that can be done the way that intended it
# or I just don't have the knowledge )
fields = ['name', 'age']
# If I use School I could get the name of the school in the title and
# its primary key, but than I don't know how to display the form and
# vise versa
template_name = 'basiccbv/student_update.html'
This is the .html file that gets me to the page where I need the form.
The link is the one calling 'basiccbv:studentupdate'
The related_name students was used here but I still can't figure out if it can
be done for adding stuff the way I want
<h1>Welcome to the school details page</h1>
<h2>School details:</h2>
<p>Name: {{ school_detail.name }}</p>
<p>Principal: {{ school_detail.principal }}</p>
<p>Location: {{ school_detail.location }}</p>
<h3>Students:</h3>
{% for student in school_detail.students.all %}
<p>{{ student.name }} who is {{ student.age }} years old.</p>
{% endfor %}
<div class="container">
<p><a href="{% url 'basiccbv:studentupdate' pk=school_detail.pk %}">Add a
student</a></p>
And here is the .html file with the form
## I changed this part bellow but nothing worked
<h1>Student coming to {{ student.school.name }}</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" class="btn btn-primary" value="Add student">
</form>
I'm really stuck and can't find any information about this but if you can help me or give any advice thank you.
The way I used to add students was with admin and for schools I used admin until I created the view for creating Schools which worked with no problems(probably because there were no foreign keys).
I think you can take this approach
Forms:
# We need to define a new form first
class StudentForm(forms.ModelForm):
class Meta:
model = Student
fields = ['name', 'age']
Views:
# we are using form view for using the form mentioned above
class StudentCreateView(FormView):
form_class = StudentForm
success_url = "/"
def get(self, request, school_id, **kwargs):
context = self.get_context_data(**kwargs) # getting context, ie: the form
context[school] = School.objects.get(pk=school_id) # updating the context with school object using the PK provided with the url
return self.render_to_response(context)
def post(self, request, school_id, **kwargs):
# overriding default implementation
form = self.get_form()
if form.is_valid():
return self.form_valid(form, school_id) # passing the pk value to form valid function to override
else:
return self.form_invalid(form)
def form_valid(self, form, school_id):
# overriding default implementation
self.object = form.save(commit=False)
self.object.school = School.objects.get(id=school_id) # saving the school information to the object
self.object.save()
return super(StudentCreateView, self).form_valid(form)
Template
# template
<h1>Student coming to {{ school.name }}</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" class="btn btn-primary" value="Add student">
</form>
Urls
path('school/student-update/<int:school_id>/', StudentCreateView.as_view(), name='studentupdate'),

Django Image Field not Updating on the Front

I am trying to get my app to validate the image upload field in my app's template and save any new image that is uploaded. Everything works fine on the admin side of things but when I make an image change on the front, it doesn't get saved.
Here is my model
def imageupload(instance, filename):
return os.path.join('static/petition-photos/', filename)
# Create your models here.
class Petition(models.Model):
title = models.CharField(max_length= 90, default="Enter petition title here")
created_on = models.DateTimeField(auto_now_add=True)
image = models.ImageField(null=False, upload_to=imageupload)
video = models.CharField(max_length=600, default="Enter an external video link")
petition = models.TextField(null=False, default="Type your petition here")
created_by = models.ForeignKey(User)
Here is my view class:
class NewPetitionView(generic.edit.CreateView):
model = Petition
template_name = 'petition/petition_form.html'
fields= ['title','petition', 'image', 'video']
success_url = '/dashboard/'
def form_valid(self, form):
form.instance.created_by = self.request.user
return super(NewPetitionView, self).form_valid(form)
And this is my form template:
{% include 'layout/header.html' %}
{% block content %}
<form action="" method="post">
{{form.as_p}}
<button type="submit">Submit</button>
{% csrf_token %}
</form>
{% endblock %}
When I upload an image to a new post or try to edit the image field of one of the posted items, I get a "This field is required" notification.
What am I doing wrong?
You need in your <form> add enctype="multipart/form-data" in order to let file upload work.
<form action="" method="post" enctype="multipart/form-data">
django doc.

Categories

Resources