I'm trying to allow the user to upload images tied to a project. I'm doing this via an inlineformset_factory.
Im serving the form to the user trough a function based view. When the user fails to fill in one (or more) of the formsets correctly formset.is_valid() returns false and the bound formsets get returned to the user along with error messages. What i'm having trouble with is that the imagefield is not returned to the bound form. So if the user has filled in 2 out of 3 forms correctly then neither of the formsets have a bound imagefield after the failed validation. I can't manage to figure out if this is working as intended or if i'm doing something wrong. If i print out request.FILES i get the following:
<MultiValueDict: {'image_set-0-filename': [<InMemoryUploadedFile: IMG_0017.jpg (image/jpeg)>], 'image_set-1-filename': [<InMemoryUploadedFile: sprite.svg (image/svg+xml)>]}>
I would like the imagefield to be bound and show the user what value the field has.
before incorrect form submission
after incorrect form submission
Hopefully you can see where my mistake is or tell me if i'm thinking about this the wrong way.
Thank you in advance
forms.py
class EndUserImageForm(forms.ModelForm):
class Meta:
model = Image
fields = ["filename", "description"]
widgets = {
"filename": forms.FileInput(attrs={"class": "form__field no--outline"}),
"description": forms.Textarea(attrs={"class": "form__field no--outline", "placeholder": "Description"})
}
EndUserImageInlineFormset = inlineformset_factory(
Project, Image, form=EndUserImageForm, extra=2)
views.py
def image_formset(request, pk):
project = get_object_or_404(Project, pk=pk)
image_formset = EndUserImageInlineFormset(request.POST or None, request.FILES or None)
context = {
"image_formset": image_formset
}
if request.method == "POST":
if image_formset.is_valid():
images = image_formset.save(commit=False)
for image in images:
image.project = project
image.save()
return HttpResponseRedirect(reverse("projects:home"))
return render(request, "projects/test.html", context)
template
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<h4>Add Images</h4>
{{ image_formset.non_form_errors }}
{{ image_formset.management_form }}
<div id="image-form-list">
{% for form in image_formset %}
{{ form.errors }}
<div class="image_form">
{{ form.filename }}
{{ form.description }}
</div>
{% endfor %}
</div>
<input type="submit" value="Save" class="button button--main activate"/>
</form>
models.py
class Image(models.Model):
project = models.ForeignKey(Project, on_delete=models.CASCADE)
filename = models.ImageField(upload_to=project_directory_path, blank=False)
description = models.CharField(max_length=350, blank=True)
created = models.DateTimeField(auto_now_add=True)
def image_formset(request, pk):
project = get_object_or_404(Project, pk=pk)
image_formset = EndUserImageInlineFormset()
if request.method == "POST":
image_formset = EndUserImageInlineFormset(request.POST or None, request.FILES or None)
if image_formset.is_valid():h
images = image_formset.save(commit=False)
for image in images:
image.project = project
image.save()
Tags will not store in database in Django
def addque(request):
if request.method == "POST":
user = request.user
if user.is_anonymous:
return redirect('addquery')
if user.is_active:
question = request.POST['question']
body = request.POST['body']
tags = request.POST['tags']
aquuid = request.user.aquuid
addquery = Question(question=question, user_id=aquuid, que_body=body, tags=tags)
addquery.save()
return redirect('addquery')
else:
return render(request, 'question/ask.html')
After giving the input the data is stored in the tags field but, not saving in the database. I can manually insert data through the admin panel successfully but not as a non-staff user. I have installed taggit and placed it in the installed_apps in settings.py. What is the issue with the code?
Tags are Many-to-Many objects and you can't add those to an object until the object has been saved. The documentation shows that you need to use .add() to add tags to a model instance. Your code should be:
addquery = Question(question=question, user_id=aquuid, que_body=body)
addquery.save()
addquery.tags.add(tags)
As an aside, you might be better served by a ModelForm which can handle the tags and all of this stuff that you're doing:
question = request.POST['question']
body = request.POST['body']
tags = request.POST['tags']
https://django-taggit.readthedocs.io/en/latest/forms.html
Use model form
In html use id of forms like this
HTML
<form action="{% url 'addquery' %}" method="post">
{% csrf_token %}
<div class="psrelative">
<input id="id_question" name="question" type="text" maxlength="300" tabindex="100" placeholder="e.g. Is there an R function for finding the index of an element in a vector?" class="s-input js-post-title-field" value="" data-min-length="15" data-max-length="150" autocomplete="off" required>
</div>
<textarea name="que_body" id="id_que_body" class="textarea-body"></textarea required>
<div class="psrelative">
<input id="id_tags" name="tags" type="text" maxlength="300" tabindex="100"
placeholder="e.g. (ruby-on-rails vba spring)" class="s-input js-post-title-field" value="" data-min-length="15" data-max-length="150" autocomplete="off" required>
</div>
<button class="review-question-btn" type="submit" tabindex="120"> Submit your question
</button>
</form>
Forms.py
from django import forms
from .models import Question
class Addqueform(forms.ModelForm):
class Meta:
model = Question
fields = ['question','que_body','tags']
Views.py
from .forms import Addqueform
def addque(request):
queform = Addqueform(request.POST)
if request.method == "POST":
user = request.user
if user.is_anonymous:
return redirect('addquery')
if user.is_active:
if queform.is_valid():
aquuid = request.user.aquuid
question = queform.cleaned_data['question']
body = queform.cleaned_data['que_body']
tags = queform.cleaned_data['tags']
addquery = Question(question=question, user_id=aquuid, que_body=body)
for tag in tags:
addquery.tags.add(tag)
addquery.save()
return redirect('addquery')
else:
queform = Addqueform()
return render(request, 'question/ask.html', {'form': queform})
else:
return render(request, 'question/ask.html', {'form': queform})
I think It will Work
I am trying to upload profile image of user from userprofile.html template but it is not uploading to that model on admin panel. I already configured the static and media settings correctly. I am able to upload from admin panel.But uploading from template isn't working please help..
#forms.py
class ImageUploadForm(forms.ModelForm):
class Meta:
model = accountUser
fields = ['profile_photo',]
#views.py
def upload_pic(request):
if request.method == 'POST':
form = ImageUploadForm(request.POST, request.FILES)
if form.is_valid():
form.save()
return redirect('success')
else:
form = ImageUploadForm()
return HttpResponseForbidden('allowed only via POST')
#user.html
<form method = "post" enctype="multipart/form-data">
{% csrf_token %}
<p>
<input id="id_image" type="file" class="" name="image">
</p>
<button type="submit">Choose Picture</button>
</form>
I am trying to edit an html page so a logged in user can favorite / bookmark a video.id
Here is the .html file
<td>
<form method='POST' action="{% url 'researcher_view_app:favourite_post' video.id %}">
{% csrf_token %}
<input type='hidden' name='video' value={{ video.id }}>
<button type='submit'>Bookmark</button>
</form>
</td>
Here is the urls.py file
path('<int:fav_id>/favourite_post', views.favourite_post, name='favourite_post'),
Here is the view.py file
def favourite_post(request, fav_id):
video = get_object_or_404(Video, id=fav_id)
if request.method == 'POST':
video.
return render(request, 'researcher_view_app/%s' % fav_id)
First you modify the models.py that has the user models
class ProjectUser(AbstractUser):
images = models.ManyToManyField(Images)
def __str__(self):
return self.email
In the .html file add the following:
{% for image in available_images %}
/* show image */
<form method='post' action="{% url 'user-image-add' %}">
{% csrf_token %}
<input type='hidden' name='image' value={{image.id}}>
<button type='submit'>bookmark</button>
</form>
{% endfor %}
In your views.py add the following method
def user_image_add(request):
user_image_form = ImageForm(request.POST or None)
if request.method == 'POST' and user_image_form.is_valid():
request.user.images.add(user_image_form.cleaned_data['image'])
return JsonResponse(status=200)
raise Http404()
Create a forms.py file in your add and add the following:
class ImageForm(forms.Form):
image = forms.ModelChoiceField(queryset=Images.objects.all())
To show those bookmarked images you can just iterate over request.user.images (it gives you a QS of Images) similar to code above.
In the urls.py add the following:
path('user-image-add/', views.user_image_add, 'user-image-add')
In models.py add a method in User model for getting bool if video is bookmarked
def is_bookmarked(self, video_id):
return self.bookmarked_videos.filter(id=video_id).exists()
simirlarly is_bookmarked can be added to Video model accepting user_id and checking video.projectuser_set.
And add the following to your .html file where users bookmarked a video
`{% if video.is_bookmarked %}`
Delete the UserProfile as you do not need it. Just make sure to have needed instance in context of view.
I am trying to allow each user to upload multiple pictures to a single blog post. I have been trying to work out the best way to do this for a few days now. What is the best practice to do this?
From what I have read, I should make a seperate image model from the blog post model and use a foreign key. Is that right?
Then there is the matter of how to allow them to upload multiple pictures at the same time. Am I right in assuming I should use something like drop zone?
Any advice on best practices for storing the photos is also welcome. I have looked at Amazon s3 and cloudinary. I want to create something which is scalable.
Any help would be much appreciated!
You'll just need two models. One for the Post and the other would be for the Images. Your image model would have a foreignkey to the Post model:
from django.db import models
from django.contrib.auth.models import User
from django.template.defaultfilters import slugify
class Post(models.Model):
user = models.ForeignKey(User)
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 Images(models.Model):
post = models.ForeignKey(Post, default=None)
image = models.ImageField(upload_to=get_image_filename,
verbose_name='Image')
You need to create a form for each model, but they will be related to each other, as in when the user is filling out the form post he has to complete the image form too for the post to successfully be posted, and we shall do that in the views, but for now your form can look something like this
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', )
class ImageForm(forms.ModelForm):
image = forms.ImageField(label='Image')
class Meta:
model = Images
fields = ('image', )
Now this is the most important part of everything, the views, because this is where uploading multiple images to a single magic happens. For us to be able to upload multiple images at once, we need multiple image fields right? That's where you fall in love with Django formsets. We will need django formsets to make this happen, you can read about formsets in the Django documentation, which I have linked :) But here is how your view should look like:
*Very important the imports
from django.shortcuts import render
from django.forms import modelformset_factory
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.http import HttpResponseRedirect
from .forms import ImageForm, PostForm
from .models import Images
#login_required
def post(request):
ImageFormSet = modelformset_factory(Images,
form=ImageForm, extra=3)
#'extra' means the number of photos that you can upload ^
if request.method == 'POST':
postForm = PostForm(request.POST)
formset = ImageFormSet(request.POST, request.FILES,
queryset=Images.objects.none())
if postForm.is_valid() and formset.is_valid():
post_form = postForm.save(commit=False)
post_form.user = request.user
post_form.save()
for form in formset.cleaned_data:
#this helps to not crash if the user
#do not upload all the photos
if form:
image = form['image']
photo = Images(post=post_form, image=image)
photo.save()
# use django messages framework
messages.success(request,
"Yeeew, check it out on the home page!")
return HttpResponseRedirect("/")
else:
print(postForm.errors, formset.errors)
else:
postForm = PostForm()
formset = ImageFormSet(queryset=Images.objects.none())
return render(request, 'index.html',
{'postForm': postForm, 'formset': formset})
In the view, we are getting both of our forms, and it will check both forms whether they are valid or not. In that way, user has to fill the form AND upload all the images which, in this case, are 3 extra=3. Only then will the post successfully get created.
Your template should look like this then:
<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 %}
{{ formset.management_form }}
{% for form in formset %}
{{ form }}
{% endfor %}
<input type="submit" name="submit" value="Submit" />
</form>
Step by step solution => Even, I was stuck too. So this is how I successfully do.
Finally, implementing below code, I achieved this
1 Note model with many Images
Multiple Uploads(at the same time, with same Choose File button, & all save together as like in Gmail file upload)
Here are my Note and Image Model- (or see full code)
class Note(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE)
title = models.CharField(max_length=30)
text = models.TextField(null=True,blank=True)
created_date = models.DateTimeField(auto_now_add=True)
last_modified = models.DateTimeField(auto_now=True)
class Images(models.Model):
note = models.ForeignKey(Note,on_delete=models.CASCADE)
image = models.ImageField(upload_to=user_directory_path,null=True,blank=True)
Here is my form (Link of doc. on multiple upload)- (or see full code)
class NoteForm(forms.ModelForm):
class Meta:
model = Note
fields = ['title','text'] #make sure to mention field here, if nothing is mentioned then all will be required.
class NoteFullForm(NoteForm): #extending form
images = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}))
class Meta(NoteForm.Meta):
fields = NoteForm.Meta.fields + ['images',]
Here is my View file- (or see full code)
def addNoteView(request):
if request.method == "POST":
#images will be in request.FILES
form = NoteFullForm(request.POST or None, request.FILES or None)
files = request.FILES.getlist('images')
if form.is_valid():
user = request.user
title = form.cleaned_data['title']
text = form.cleaned_data['text']
note_obj = Note.objects.create(user=user,title=title,text=text) #create will create as well as save too in db.
for f in files:
Images.objects.create(note=note_obj,image=f)
else:
print("Form invalid")
And, finally my Html file (be sure to bind files as said in docs)- (or see full code)
<form action="{% url 'note:add_note' %}" method="post" class="note-form" enctype="multipart/form-data">{% csrf_token %}
<div class="form-group">
<label for="note-title">Title</label>
<input type="text" name="title" class="form-control" id="note-title" placeholder="Enter Title" required>
</div>
<div class="form-group">
<label for="note-description">Description</label>
<textarea type="text" name="text" class="form-control" id="note-description" placeholder="Description here"></textarea>
</div>
<div class="form-group">
<label for="note-image">Images</label>
<input type="file" name="images" class="form-control-file" id="note-image" multiple>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
The answer is very simple. So much of code is not required.
HTML File
<input type="file" name = "files" multiple />
VIEWS.PY
files = request.FILES.getlist('files')
for f in files:
a = Image(image=f)
a.save()
I had a similar problem. In my model, an article must have a thumbnail(image), then I set five more fields for optional images. The problem came in when after applying the safe filter, the image source would not render because it was not HTML anymore,
<img src="{{article.image1.url}}" class="some class">
would not work. A temporary solution I used was to name the images according to the title of the article. If I am writing on "django filters", I would name my additional images djangofiltersimage1.png or djangofiltersimage2.png, this helps since in my model each article has a unique title. I then changed the image source to:
<img src="/media/djangofiltersimage1.png" class="some class">
The only issue is the strict naming of images. You can upload more images by creating a model for Blog Images. I am still looking for another solution in the meantime.
You can check out my blog here
getlist name and html input name fiels should be same
for models.py
class studentImage(models.Model):
image = models.ImageField(upload_to='media/')
def __str__(self):
return self.image
for views.py
def studentImageView(request):
if request.method == "POST":
images = request.FILES.getlist('images')
for image in images:
photo = studentImage.objects.create(image=image,)
photo.save()
return render(request, 'image.html')
for template
<form method="post" enctype="multipart/form-data" >
{% csrf_token %}
<input required name="images" type="file" multiple >
<button class="btn btn-primary" type="submit">Upload</button>
</form>