ModelForm Upload Multiple Images: not saving images in static folder - django

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!

Related

Django updateview (cbv and fbv) to upload multiple files not working

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)

Generic DeleteView is returning django.db.models.query_utils.DeferredAttribute object at 0x04725628 - Django

Disclaimer: I'm just a novice trying to learn Django
Hello, I'm trying to refactor my code and modify all the views that I have created to be Class Based Views.
I have an issue loading a form with DeleteView that is showing the data and at the same time is disabled.
I have some success and the only thing that I cannot figure out how to do is to show the data instead of the message that appears now "<django.db.models.query_utils.DeferredAttribute object at 0x04725628>"
+models.py:
class Note(models.Model):
title = models.CharField(max_length=30)
image_url = models.URLField()
content = models.TextField()
owner = models.ForeignKey(Profile, default=8, on_delete=models.CASCADE)
def get_absolute_url(self):
return reverse(self.pk)
def __str__(self):
return f'{self.title}'
+forms.py
class NoteForm(forms.ModelForm):
class Meta:
model = Note
exclude = ('owner',)
class DeleteNoteForm(NoteForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for (_, field) in self.fields.items():
field.widget.attrs['readonly'] = True
field.widget.attrs['disabled'] = True
+views.py
class DeleteNoteView(DeleteView):
model = Note
template_name = 'note-delete.html'
form_class = DeleteNoteForm
success_url = reverse_lazy('home page')
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
data['form'] = self.form_class(instance=self.model)
return data
+urls.py
path('delete/<int:pk>/', views.DeleteNoteView.as_view(), name='delete note'),
+template
<!--note delete data form-->
<div class="form">
<form method="POST">
{{ form }}
{% csrf_token %}
<input type="submit" value="Delete"/>
</form>
</div>
<!--end note delete data form-->
If I use my view it works fine, but I want to modify it.
def delete_note(request, pk):
note = Note.objects.get(pk=pk)
if request.method=='GET':
note_form = DeleteNoteForm(instance=note)
context = {
'note_form': note_form
}
return render(request, 'note-delete.html', context)
else:
note.delete()
return redirect('home page')
Could someone tell me where I'm wrong and how I can fix it or at least provide me a link with information to understand why this is happening?
You are passing a reference to the model class in your DeleteNoteView, whereas you should use the object that is removed, so:
class DeleteNoteView(DeleteView):
model = Note
template_name = 'note-delete.html'
form_class = DeleteNoteForm
success_url = reverse_lazy('home page')
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
# user self.object instead of self.model &downarrow;
data['form'] = self.form_class(instance=self.object)
return data
I would also advise to filter the QuerySet such that it is impossible for another user (a user that is not the owner of a Note to remove that Note:
from django.contrib.auth.mixins import LoginRequiredMixin
class DeleteNoteView(LoginRequiredMixin, DeleteView):
model = Note
template_name = 'note-delete.html'
form_class = DeleteNoteForm
success_url = reverse_lazy('home page')
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(
owner=self.request.user
)
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
data['form'] = self.form_class(instance=self.object)
return data

Django: Page not found (404) when posting form data

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

how to update formset

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()

How to render a POST and make it show up on another page? in Django form

I'm trying to create Car Rental website similar to Hyrecar. I created a form according to the Django tutorial "Working with forms", but I don't know how to render information I got from the POST forms. I want to make information(Booking Name,rental price ...etc) that I got from POST show up the car and its detail which is booked. Car is foreign key field in booking.i want to redirect it to the page showing that car which is booked .
for eg
Booking name : xyz
Rental Price : 123
CAr : carimage.jpg
4 .
I want to redirect it to the page pop. if the user booked a car and post the form . after that redirect it to the pop.html page and show the booking detail that the user posted now .
Forms.py
class BookingForm(ModelForm):
class Meta:
model = Booking
widgets = {
'times_pick': forms.TimeInput(attrs={'class':'timepicker'}),
}
fields = ('booking_name','rental_price','book_car','customer_name','times_pick',)
urls.py
[
url(r'^booking/',views.BookingView.as_view(),name='car_booking'),
url(r'^pop/$',views.PopView.as_view(),name='pop'),
]
views.py
class CarDetailView(DetailView):
context_object_name = 'car_details'
model = models.Car
template_name = 'buggy_app/car_detail.html'
class BookingView(FormView):
template_name = 'buggy_app/booking.html'
form_class = BookingForm
models = Booking
def form_valid(self, form):
form.save()
return super(BookingView, self).form_valid(form)
success_url = reverse_lazy('index')
def get_context_data(self, **kwargs):
# kwargs['car'] is the car booking now!
try:
kwargs['car'] = Car.objects.get(id=self.request.GET.get('car', ''))
except (Car.DoesNotExist, ValueError):
kwargs['car'] = None
return super(BookingView, self).get_context_data(**kwargs)
def get_initial(self):
initial = super(BookingView, self).get_initial()
if 'car' in self.request.GET:
try:
initial['book_car'] = Car.objects.get(id=self.request.GET['car'])
except (Car.DoesNotExist, ValueError):
pass
return initial
booking.html
<form method="POST">
{% csrf_token %}
{% bootstrap_form form %}
<input type="submit" class='btn btn-primary' value="Submit">
</form>
Try like this
def save_form(request):
args = {}
form = BookCarForm(request.POST)
if form.is_valid():
book = form.save(commit=False)
book.book_car_mark = request.POST.get('car_mark')
book.book_car_mmodel = request.POST.get('car_model')
book.book_car_year = request.POST.get('car_year')
book.book_car_mark = request.POST.get('car_mark')
form.save()
try:
args['book'] = Book.objects.get(id=book.id)
except:
args['book'] = None
if args['book'] is not None:
return render(request, 'your_template.html', args)
else:
return HttpResponseRedirect('/your/url/to-booking-form/')
Name of fields as name of models and templates are abstract, so it's just a working mechanism scheme