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>
Related
I have written the code for file upload but it is not displaying on my destination page.
Please help me edit my code or suggest how to fix this issue.
The rest of the fields are displaying but not the file field
My models.py
class Help(models.Model):
researcher = models.CharField(max_length=100)
study = models.CharField(max_length=500)
date = models.DateTimeField(auto_now_add=True)
document = models.FileField(upload_to='documents/', null=True, blank=True)
forms.py
from django import forms
from .models import Help
from django.forms import ModelForm
class AboutHelp(forms.ModelForm):
class Meta:
model = Help
fields = '__all__'
source page
<form action="{% url 'lazer.views.about_experiment' exp.link_name %}" method="POST" name="form">
{% csrf_token %}
<label>Researcher Name(s):
<input type="text" name="researcher"><br>
<lable>Study Summary
<textarea rows="10" cols="50" placeholder="Start typing..." maxlength="500" class="form-control" name="study"></textarea>
<br>
<label>Upload your IRB approval letter:
<input type ="file" id="irb-file" class="file_input" name="document"></label>
<br>
<input type = "submit" value="Submit" class="btn btn-primary" />
</form>
views.py
def about_experiment(request, ex_link_name):
researcher = None
study = None
posts = None
exp = get_object_or_404(Experiment,link_name = ex_link_name)
high_scores = ScoreItem.objects.filter(experiment=exp,active=True)
context = {
'request': request,
'exp':exp,
'high_scores': high_scores,
'awards':AwardItem.objects.filter(experiment=exp,visible=True),
}
if exp.about_file:
context['about_file'] = settings.EXPERIMENT_DIRS+exp.about_file.get_include_path()
return render(request, 'about_experiment.html', context)
if request.method == 'POST':
form = AboutHelp(request.POST, request.FILES)
posts = Help.objects.filter().order_by('-date')[0]
if form.is_valid():
obj = form.save(commit = False)
obj.save()
researcher = form.cleaned_data['researcher']
study = form.cleaned_data['study']
document = form.cleaned_data['document']
else:
form = AboutHelp()
posts = Help.objects.filter().order_by('-date')[0]
return render(request, 'about_experiment.html', {'posts': posts})
return render(request, 'about_experiment.html', {'posts': posts})
destination page
<h4><b>{{ posts.researcher }}</b></h4>
<p>{{ posts.study }}</p>
<p>Uploaded file is : {{ posts.document }}</p>
Have you checked your file is been saved? And I think you have not understood the use of Django forms yet.Here's how to .You're creating the form but you're displaying fields manually and in view, you getting the data by forms.cleaned_data. Also, to save files you need to define <form enctype="multipart/form-data" > Here's why
I am quite new to django and although my enthusiasm I am stuck to the below:
I have the model
class Task(models.Model):
Taskdetails = models.CharField(max_length=500, null=True)
asset = models.ForeignKey('Asset', null=True)
def __str__(self):
return str(self.id)
and I have created several objects with the ModelForm technique
I want to create a template that a user can choose an asset and by pressing the button to display all the tasks that are connected with this asset. I mention that an asset has many tasks. I am trying to do that using Django ModelForm.
So, I created first of all the form
class HistoryForm(ModelForm):
class Meta:
model = Task
fields =('asset',)
Then I created the view "history" where a user can choose the asset that is interested:
def history(request):
if request.method == "POST":
hist_form = HistoryForm(request.POST)
if hist_form.is_valid():
hist = hist_form.save(commit=False)
hist.save()
return ('results')
else:
hist_form = HistoryForm()
return render(request, 'TaskTrace/history.html', {'hist_form': hist_form}
The html tag (part of it) is the below
{{ hist_form }}
<p> <button type="submit" > Results</button></p>
Then I want to take the choice that the user made and transfer it to the next view ('results')
def results(request):
tasks = Task.objects.filter(asset=request.POST.get('asset', None))
return render(request, 'Tasktrace/results.html', {'tasks': tasks})
And the hmtl template is
{% for task in tasks %}
<div>
<p><u>{{ task }} </p>
</div>
{% endfor %}
The problem is that I do not get any tasks. I believe that my big problem is to transfer the value from the first view to the other.
Is it the correct way to do the filtering in ModelForms? I probably miss something here
I really appreciate your help
You are very very close.
Inside the history.html write the form like this:
<form method="POST" action="{% url 'results' %}">{% csrf_token %}
{{ hist_form }}
<input type="submit" name="submit_results" value="Results" />
</form>
Now, the above form will be submitted to the results view and there you will get any POST data (as you already do, request.POST.get('asset', None)).
At the moment I have some Posts to show to the users. The GenericView handling this page is a DetailView and I've already passed FormMixin into it to handle Comment functionality.
Now I want to add a Flag or Report form to bottom of each Post. I've found some ways to pass two different forms to a single generic view, but I found them messy and django suggest not to do such complex things. My question is how would I do this task?
Actually I was trying to render the form manually but I couldn't figure out how to pass reason id to the action of the form.
Report's Model:
class Report(models.Model):
reporter = models.ForeignKey(User, related_name='reporters')
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
object = GenericForeignKey('content_type', 'object_id')
reason = models.IntegerField(choices=REASON_CHOICES, default=BROKEN_LINK)
Report's Form:
BROKEN_LINK, RUDE, BAD_TRANSLATE, IRR = range(4)
REASON_CHOICES = (
(BROKEN_LINK, 'Broken_link'),
(RUDE, 'Rude'),
(BAD_TRANSLATE, 'Bad_translate'),
(IRR, 'Irr')
)
class ReportForm(forms.ModelForm):
class Meta:
model = Report
fields = ['reason']
widgets = {
'reason': forms.RadioSelect,
}
Report views.py:
( report_object and ReportCreateView are doing the same job, the first one was for the time that I decide to don't use generic views to create handle the form )
def report_object(request, model, object_id, reason):
if request.post == POST:
...
class ReportCreateView(CreateView):
model = Report
form_class = ReportForm
template_name = "forms/report_form.html"
def form_valid(self, form):
...
In textdeatilview I define reasons as:
context['reasons'] = REASON_CHOICES
Here's what I was thinking to do inside template:
<form class="report" method="POST" id="{{ post.id }}" action="{% url 'report_create' model="post" object_id=post.id reason=??? %}">
{% csrf_token %}
{% for id, value in reasons %}
<p><input type="radio" name="reason" id="" value="{{ id }}" />{{ value }}</p>
{% endfor %}
<input type="submit" value="Add">
</form>
Is this the right approach? If so, how should I fix it?
No. The reason ID is part of the submitted form data, it's not part of the URL and it doesn't need to be a parameter to the view.
I have a model:
class PartnerPrefs(models.Model):
partner = models.ForeignKey(Partner)
theme = models.IntegerField()
email = models.EmailField()
logo_file = models.ImageField(upload_to='logos/')
And my forms:
class PartnerPrefsForm(ModelForm):
theme = forms.ChoiceField(
choices=THEME_CHOICE,
widget=forms.Select(),
initial='1',
)
class Meta:
model = PartnerPrefs
exclude = ('partner',)
And my views:
...
if request.method == 'POST':
prefsform = PartnerPrefsForm(request.FILES, request.POST)
if prefsform.is_valid():
# do stuff
And finally my template:
<form enctype='multipart/form-data' form action='.' method='post'>
{% csrf_token %}
{{ prefsform.as_p }}
<input type="submit" value="Submit" />
</form>
Whenever I submit the form all field come back with This field is required.... If I eliminate the ImageField from the form, then it works fine. I cannot find what the problem is.
The issue turned out to be the order in which request.POST and request.FILES are passed to the from. request.POST must go first.
I'm trying to get uploading working with Django. I don't get an error when I try to upload it, and the new Item is saved, but there is no image. The other form data is all there. I've been googling and on stack overflow for the past 2 days and cannot find a fix. I don't even know where it's going wrong! Any advice would be amazing. Thanks so much. :)
from views.py:
if request.method == 'POST':
form = ItemForm(request.POST, request.FILES, user=request.user, forsale=True)
if form.is_valid():
new_item = form.save()
return item_view(request, marketplace_id, new_item.id)
else:
form = ItemForm(user=request.user)
from models.py:
class Item(models.Model):
photo = StdImageField(upload_to='images/', blank=True, size=(500, 500, True), thumbnail_size=(210, 150, True))
user = models.ForeignKey(User)
...
def __unicode__(self):
return self.name
class ItemForm(ModelForm):
class Meta:
model = Item
fields = ['name', 'description', 'price', 'shipping', 'photo', 'section']
def __init__(self, *args, **kwargs):
self._user = kwargs.pop('user')
super(ItemForm, self).__init__(*args, **kwargs)
from the template:
<form enctype="multipart/form-data" action="" method="post" data-ajax="false">{% csrf_token %}
{% for field in form %}
<div class="fieldWrapper" data-role="fieldcontain">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endfor %}
<input type="submit" value="Save">
</form>
As I see it, the template is correct. The Item model seems fine, StdImageField is a wrapper for ImageField. It works fine in the admin panel.
The Form seems fine as well, nothing special.
In my views.py fine, I check for a POST request, call the ItemForm and pass in request.POST and request.FILES (and forsale which is a boolean). I check if the form is valid, if so, I save it. Why isn't it working? The Item saves but in the admin panel there is no file associated with 'photo'.
Check that MEDIA_ROOT setting joined with "images/" is a valid and writable path for Django process.
Also using pdb
import pdb; pdb.set_trace()
can help you in the process of finding the issue, (f.i. have a look at request.FILES to see if everything looks ok there during post).