Django: how to make POST request with form data? - django

I'm learning Django and am running into an issue posting a piece of data to the database. Here's my code:
urls.py
urlpatterns = [
path("", views.index, name="index"),
...
path("listing/<int:listing_id>", views.display_listing, name="listing")
]
Models.py
class Bid(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='usr_bids')
price = models.DecimalField(max_digits=5, decimal_places=2)
class Listing(models.Model):
title = models.CharField(max_length=50)
bids = models.ManyToManyField(Bid, blank=True, related_name='bids')
price = models.DecimalField(max_digits=7, decimal_places=2)
closed = models.BooleanField(default=False)
Forms.py
class BidForm(ModelForm):
class Meta:
model = Bid
fields = ['price']
views.py
def display_listing(request, listing_id):
listing = Listing.objects.get(pk=listing_id)
if not request.user.is_authenticated:
return HttpResponseRedirect(reverse('login'))
if request.method == "POST":
user = User.objects.get(username=request.user)
if request.POST.get("button") == "Watchlist":
if not user.watchlist.filter(listing=listing):
watchlist = Watchlist()
watchlist.user = user
watchlist.listing = listing
watchlist.save()
else:
user.watchlist.filter(listing=listing).delete()
return HttpResponseRedirect(reverse('listing', args=(listing.id, )))
if not listing.closed:
if request.POST.get("button") == "Close":
listing.closed = True
listing.save()
else:
price = float(request.POST["price"])
bids = listing.bids.all()
if user.username != listing.creator.username:
if price <= listing.price:
return render(request, 'auctions/listing.html',{
'listing': listing,
'form': BidForm(),
'message': 'Increase your bid.'
})
form = BidForm(request.POST)
if form.is_valid():
bid = form.save(commit=False)
bid.user = user
bid.save()
listing.bids.add(bid)
listing.price = price
listing.save()
else:
return render(request, 'auctions/listing.html', {
'form': form
})
return HttpResponseRedirect(reverse('listing', args=(listing.id, )))
else:
return render(request, 'auctions/listing.html', {
'listing': listing,
'form': BidForm(),
'comments': listing.comments.all()
})
auction/listings.html
<div>
<form action="{% url 'listing' listing.id %}" method="POST">
{% csrf_token %}
<div class="form-group">
<label for="bid">{{ listing.bids.count }} bid(s) so far. You have the best bid!.</label>
</div>
<div class="form-group">
{{ form }}
</div>
<div class="form-group">
<input type="submit" name="button" class="btn btn-primary" value="Send Bid">
</div>
</form>
</div>
I populate a value (e.g. 300) in the price field. Then, I click the "Send Bid" button. Unfortunately, nothing happens.
Does anyone see why I'm unable to save a price to the database? Thanks in advance for taking a look!

At a first glance, you should rather have something like:
<form method="post">
{% csrf_token %}
{{ form }}
<input type="submit" name="button" class="btn btn-primary" value="Send Bid">
</form>
in your template.

Related

Django Error ''Field 'id' expected a number but got <Transfernews: Transfernews object (1)>.''

I am getting this error when trying to submit comments. Can anyone please tell me how to fix this?
My models.py:
class Transfernews(models.Model):
player_name = models.CharField(max_length=255)
player_image = models.CharField(max_length=2083)
player_description = models.CharField(max_length=3000)
date_posted = models.DateTimeField(default=timezone.now)
class Comment(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
transfernews = models.ForeignKey(Transfernews, related_name="comments", on_delete=models.CASCADE)
body = models.TextField()
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
return '%s - %s' % (self.transfernews.player_name, self.user.username)
My forms.py:
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('body',)
My views.py:
def transfer_targets(request):
transfernews = Transfernews.objects.all()
news = request.POST.get("transfer_id", None)
form = CommentForm(request.POST or None)
if form.is_valid():
new_comment = form.save(commit=False)
new_comment.user = User.objects.get(id=request.user.id)
new_comment.transfernews_id = Transfernews.objects.get(id=news)
new_comment.save()
return redirect(request.path_info)
return render(request, 'transfernews.html', {'transfernews': transfernews, 'form': form})
My html file:
{% for transfer in transfernews %}
<div>Comment and let us know your thoughts</div>
<form method="POST">
{% csrf_token %}
<input type="hidden" name="transfer_id" value="{{ transfer.id}}">
<div class="bg-alert p-2">
<div class="d-flex flex-row align-items-start">
</div>
<div class="mt-2 text-right">
{{ form|crispy }}
<br>
<button class="btn btn-primary btn-sm shadow-none" type="submit">Post comment</button>
<button class="btn btn-outline-primary btn-sm ml-1 shadow-none" type="button">Cancel</button>
</div>
</div>
</form>
{% endfor %}
In your view you should work with:
new_comment.transfernews_id = news
or:
new_comment.transfernews = Transfernews.objects.get(id=news)
but the latter is less efficient since you make an extra database query.
You thus can rewrite the view to:
from django.contrib.auth.decorators import login_required
#login_required
def transfer_targets(request):
transfernews = Transfernews.objects.all()
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
news = request.POST['transfer_id']
form.instance.user = request.user
form.instance.transfernews_id = news
form.save()
return redirect(request.path_info)
else:
form = CommentForm()
return render(request, 'transfernews.html', {'transfernews': transfernews, 'form': form})
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].

comment not being submitted by my django form

I want my users to be able to add comments under a transfernews [I am creating a sports related website], I tried this code, but for some reason I am getting this error TypeError at /transfernews/ Field 'id' expected a number but got <Transfernews: Transfernews object (3)>., I can add comments manually from the admin page but can't from the comment form. Can anyone please tell me how to fix my code?
My models.py:
class Transfernews(models.Model):
player_name = models.CharField(max_length=255)
player_image = models.CharField(max_length=2083)
player_description = models.CharField(max_length=3000)
date_posted = models.DateTimeField(default=timezone.now)
class Comment(models.Model):
user = models.ForeignKey(to=User, on_delete=models.CASCADE)
transfernews = models.ForeignKey(Transfernews, related_name="comments", on_delete=models.CASCADE)
body = models.TextField()
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
return '%s - %s' % (self.transfernews.player_name, self.user.username)
My forms.py:
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('body',)
My views.py:
def transfer_targets(request):
transfernews = Transfernews.objects.all()
news = request.POST.get("transfer_id", None)
form = CommentForm(request.POST or None)
if form.is_valid():
new_comment = form.save(commit=False)
new_comment.user = User.objects.get(id=request.user.id)
new_comment.transfernews_id = Transfernews.objects.get(id=news)
new_comment.save()
return redirect(request.path_info)
return render(request, 'transfernews.html', {'transfernews': transfernews, 'form': form})
My html file:
{% for transfer in transfernews %}
<h2>Comments...</h2>
{% if not transfer.comments.all %}
No comments Yet...
{% else %}
{% for comment in transfer.comments.all %}
<strong>
{{ comment.user.username }} - {{ comment.date_added }}
</strong>
<br/>
{{ comment.body }}
<br/><br/>
{% endfor %}
{% endif %}
<hr>
<div>Comment and let us know your thoughts</div>
<form method="POST">
{% csrf_token %}
<input type="hidden" value="{{ transfer.id}}">
<div class="bg-alert p-2">
<div class="d-flex flex-row align-items-start">
</div>
<div class="mt-2 text-right">
{{ form|crispy }}
<br>
<button class="btn btn-primary btn-sm shadow-none" type="submit">Post comment</button>
<button class="btn btn-outline-primary btn-sm ml-1 shadow-none" type="button">Cancel</button>
</div>
</div>
</form>
{% endfor %}
Try using new_comment.transfernews = transfernews instead of new_comment.transfernews_id = transfernews.id.
Also change redirect(request.path_info) to redirect(reverse('<app_name>:<url_namespace>')).
You have to add a name attribute to your hidden input, then only the hidden input data will be included in the form data.
<input type="hidden" name="transfer_id" value="{{ transfer.id}}">
and in your transfer_targets view make these changes
def transfer_targets(request):
transfernews = Transfernews.objects.all()
transfernews = request.POST.get("transfer_id", None)
form = CommentForm(request.POST or None)
if form.is_valid():
new_comment = form.save(commit=False)
new_comment.user = User.objects.get(id=request.user.id)
new_comment.transfernews_id = transfernews.id
new_comment.transfernews_id = Transfernews.objects.get(id=transfernews)
new_comment.save()
return redirect(request.path_info)
return render(request, 'transfernews.html', {'transfernews': transfernews, 'form': form})

django - pass multiple instance into form and save it in DB

I have a view where they are multiple posts and I want when the user like one of them, the form take the user_id and the post_id and save it into the DB. This is th Models.py:
class LikePost(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Posts, on_delete=models.CASCADE)
def __str__(self):
return '{} - {}'.format(self.user.username, self.post.name)
Forms.py:
class LikePostForm(forms.ModelForm):
class Meta:
model = LikedShops
fields = ['user', 'post']
widgets = {
'user': forms.HiddenInput(),
'post': forms.HiddenInput()
}
Views.py:
def posts(request):
if request.method == 'POST':
form = LikePostForm(request.POST)
if form.is_valid():
u = form.save(commit=False)
u.user = request.user
u.save()
return redirect('posts')
else:
form = LikePostForm()
context = {
'posts': Posts.objects.all(),
'form': form
}
return render(request, "posts.html", context)
and this the form in posts.html:
{% for post in posts %}
<div class="col-md-3">
<article class="card mb-4">
<header class="card-header">
<h4 class="card-title"><b>{{ post.name }}</b></h4>
</header>
<img style="width: 100%; height: 150px;" class="card-img" src="{{ post.image.url }}"/>
<div class="card-body">
<p class="card-text">{{ post.description }}</p>
</div>
{% if user.is_authenticated %}
<div class="card-footer">
<div class="row">
<div class="col">
<form action="/posts/" method="post">
{% csrf_token %}
{{ l_form|crispy }}
<button type="submit" class="btn btn-outline-success">Like</button>
</form>
</div>
</div>
</div>
{% endif %}
</article><!-- /.card -->
</div>
{% endfor %}
This is my edit, I did what you said, I made changes to:
forms.py:
class Meta:
model = Liked
fields = ['user', 'post']
widgets = {
'user': forms.HiddenInput(),
'post': forms.HiddenInput()
}
posts.html:
<form action="/posts/" method="post">
{% csrf_token %}
<input type="hidden" name="post" value="{{ post.pk }}">
{{ l_form|crispy }}
<button type="submit" class="btn btn-outline-success">Like</button>
</form>
views.py:
def posts(request):
if request.method == 'POST':
l_form = LikePostForm(request.POST, instance=request.user.profile)
if l_form.is_valid():
u = l_form.save(commit=False)
u.post = Posts.objects.filter(pk=l_form.cleaned_data.get('post')).first()
u.save()
messages.success(request, f"Form is valid!")
else:
messages.warning(request, f'Form is not valid! {request.POST}')
else:
l_form = LikePostForm(instance=request.user.profile)
context = {
'post': Posts.objects.all(),
'l_form': l_form
}
return render(request, "posts.html", context)
Now when I click the Like button, I got this message **Form is not valid! <QueryDict: {'csrfmiddlewaretoken': ['cNk9ZDS33Nj0l95TBfwtedL1jjAbzDSrH15VjMNZAcxjQuihWNZzOkVnIyRzsjwN'], 'post': ['1', ''], 'user': ['1']}>**
There are a couple of issues with your code.
First, the __str__() method should return a string and not a tuple
class LikePost(models.Model):
...
def __str__(self):
return '{} - {}'.format(self.user.username, self.post.name)
Second, there is a typo; change Pots to Posts:
context = {
'posts': Posts.objects.all(),
'form': form,
}
return render(request, "posts.html", context)
And third and last, the line u.post = request.post is throwing the error you mention, because the request object has no attribute post.
So change your form code to add the post in hidden state (I used fields instead of exclude):
class LikePostForm(forms.ModelForm):
class Meta:
model = LikePost
fields = ['post', ]
widgets = {
'post': forms.HiddenInput(),
}
and then change your view:
form = LikePostForm(request.POST)
if form.is_valid():
u = form.save(commit=False)
u.user = request.user
u.save()
After edit to the question:
Try adding post.pk as a hidden input in your form:
<form action="/posts/" method="post">
{% csrf_token %}
<input type="hidden" name="post" value="{{ post.pk }}">
{{ l_form|crispy }}
<button type="submit" class="btn btn-outline-success">Like</button>
</form>
or you can also do in your view:
u.post = Posts.objects.filter(pk=form.cleaned_data.get('post')).first()

Uploading multiple images in Django for a single post

I am beginner in django . I would like to make an application that allows a user to record examinations and related images.
So i try to uploading multiple images in Django for a single post according this topic http://qasimalbaqali.com/uploading-multiple-images-in-django-for-a-single-post/.
But nothing happens when I press the button
I explain my models. A document is a generic class. An exam is a document, a document contains files.
class Document(models.Model):
class Meta:
db_table = 'Document'
slug = models.SlugField(max_length=100)
user = models.ForeignKey(User)
level = models.ForeignKey(ClassLevel, null=False, default=1)
school = models.ForeignKey(School, null=False, default=1)
nb_views = models.IntegerField(default=0)
name = models.CharField(max_length=100)
matter = models.ForeignKey(ClassTopic, null=False, default=1)
status = models.IntegerField(choices=DOCUMENT_STATUS, default=1)
creation_date = models.DateTimeField(auto_now_add=True)
deletion_date = models.DateTimeField(auto_now_add=False, default=None, null=True)
def __unicode__(self):
return self.name + " (" + str(self.status) + ") " + self.school.name
class DocumentFile(models.Model):
class Meta:
db_table = 'DocumentFile'
file = models.FileField(upload_to="photo/", null=True)
document = models.ForeignKey(Document)
def __unicode__(self):
return self.file
class Exam(Document):
class Meta:
db_table = 'Exam'
year_exam = models.IntegerField(choices=EXAM_YEAR_CHOICES, default=1)
mock_exam = models.IntegerField(choices=EXAM_TYPE, default=1)
def __unicode__(self):
return self.name + " " + self.matter
I create two forms. For exam and for file.
class UploadFileForm(ModelForm):
#description = forms.CharField(max_length=30)
file = forms.FileInput()
helper = FormHelper()
helper.form_id = 'file-input'
helper.form_show_labels = False
helper.layout = Layout(PrependedText('file', "", placeholder=""))
#helper.layout.insert(1, HTML("<input type='file' class='file' multiple data-show-upload='false' data-show-caption='true'>"))
class Meta:
model = DocumentFile
fields = ('file',)
#exclude = ("file_type", "file_path", "document")
class CreateExamForm(forms.ModelForm):
helper = FormHelper()
helper.form_id = 'CreateExam'
helper.form_show_labels = False
helper.layout = Layout(
PrependedText("matter", "", ""),
PrependedText("level", "<small class='text-warning'>Selectionner la classe. </small>", ""),
PrependedText("school", "<pre><small>Selectionner l\'établissement. </small></pre>", css_class="selectpicker"),
PrependedText("year_exam", ""),
PrependedText("mock_exam", ""))
class Meta:
model = Exam
exclude = ("slug", "user", "nb_views", "name", "status", "creation_date", "deletion_date")
My view
def createexam(request):
# Creation du formulaire + upload des images
doc_form = CreateExamForm(auto_id=True)
# Création du formset avec n itération : extra=2
file_form_set = modelformset_factory(DocumentFile, form=UploadFileForm, extra=1)
# Récupération du formulaire géré par le mécanisme formset
#formset = sortedfilesform()
if request.method == "POST":
doc_form = CreateExamForm(request.POST)
files_form = file_form_set(request.POST, request.FILES, queryset=DocumentFile.objects.none())
if doc_form.is_valid() and files_form.is_valid():
doc_save = doc_form.save(commit=False)
doc_save.user = request.user
for fileform in files_form.cleaned_data:
image = fileform['file']
one_image = DocumentFile(document=doc_save, file_value=image)
one_image.save(commit=False)
transaction.commit()
msg = FORM_PROPERTIES.FORM_EXAM_STORED.replace("user", request.user.nickname)
messages.add_message(request, messages.SUCCESS, msg)
form = LoginForm()
context = {'form': form}
return render_to_response(template_name='login.html', context=context, context_instance=RequestContext(request))
else:
messages.add_message(request, messages.SUCCESS, FORM_PROPERTIES.FORM_EXAM_ERROR)
context = {'doc_form': doc_form, 'file_form_set': file_form_set, }
return render(request, 'createexam.html', context)
else:
context = {'doc_form': doc_form, 'file_form_set': file_form_set, }
return render(request, 'createexam.html', context)
my url.py
urlpatterns = [
# Examples:
url(r'^$', home, name='home'),
url(r'^admin/', include(admin.site.urls)),
url(r'^$', home),
url(r'^home', home),
url(r'^login', login),
url(r'^logout', logout),
url(r'^register$', register),
url(r'^createexam$', createexam),
url(r'^account/reset_password', reset_password, name="reset_password"),
]
My template is simple
<div id="login-overlay" class="modal-dialog">
<div class="row">
<div class="panel panel-info" >
<div class="panel-heading">
<div class="panel-title">Créer un examen</div>
</div>
<div class="panel-body" >
<div class="col-sm-12">
<form id="post_form" method="POST" action='.'
enctype="multipart/form-data">
{% csrf_token %}
{% for hidden in doc_form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% for field in doc_form %}
{{ field }} <br />
{% endfor %}
{{ file_form_set.management_form }}
{% for form in file_form_set %}
{% crispy form %}
{% endfor %}
<input type="submit" value="Add recipe" class="submit" />
</form>
</div>
</div>
</div>
</div>
Everything seems good form me. But my submit does no think.
I 'm on it for two days.
I read and tried many things. Does anybody can help me please (Sorry for my english)
There are a couple of things wrong here.
Firstly you are not showing the form errors in the template, so the user has no way of knowing why their submission is invalid. Make sure you either do {{ field.errors }} after every field, or do {{ form.errors }} for the whole form at once; and remember to do this both form the main form and the image formset.
Secondly, you're saving the forms with commit=False, so they are not persisted to the database. It's fine to do that if you want to add extra data not included in the form, but you then need to remember to actually persist the object by calling eg doc_save.save().
I solved my problem when i put my main form body in <table> ... </table>
<form id="CreateExamForm" method="POST" enctypr="multipart/form-data">
{% csrf_token %}
<table>
<div class="panel panel-success">
<div class="panel-heading">
<h3 class="panel-title">Classe - Matière - Date</h3>
<span class="pull-right"><i class="glyphicon glyphicon-chevron-up"></i></span>
</div>
<div class="panel-body">
{% crispy doc_form %}
{{ file_form_set.management_form }}
{% for f_form in file_form_set %}
<div class="form-inline">
{% crispy f_form %}
</div>
{% endfor %}
</div>
</div>
</table>
<input type="submit" value="Add recipe" class="submit" />
</form>

Handle multiple modelforms in one html form

A user will have photos which will be related with their specific album.
So this was the model for that:
class Album(models.Model):
user = models.ForeignKey(User)
title = models.CharField(max_length=200)
pub_date = models.DateTimeField(auto_now_add=True, auto_now=False)
update = models.DateTimeField(auto_now_add=False, auto_now=True)
class Photo(models.Model):
photo_privacy = models.CharField(max_length=1,choices=PRIVACY, default='F')
user = models.ForeignKey(User)
caption = models.TextField()
image = models.ImageField(upload_to=get_upload_file_name)
pub_date = models.DateTimeField(auto_now_add=True, auto_now=False)
Views.py:
def create_album(request, user_name):
user = User.objects.get(username=unquote(user_name))
if request.method=='POST':
pform = AlbumPhotoForm(request.POST, request.FILES)
aform = AlbumForm(request.POST)
p_valid = pform.is_valid()
a_valid = aform.is_valid()
if p_valid and a_valid:
photo = pform.save(commit=False)
album = aform.save(commit=False)
photo.user = user
album.user = user
album.save()
photo.album = album
photo.save()
return HttpResponseRedirect('/'+user.username+'/photos')
else:
return render(request, 'create_album.html',{
'pform':pform,
'aform':aform
})
else:
pform = AlbumPhotoForm()
aform = AlbumForm()
return render(request, 'create_album.html', {
'pform':pform,
'aform':aform
})
And the form:
<form action="/{{ user.username }}/create_album/" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ aform.as_p }}
{{ pform.as_p }}
<input type="submit" value="Create and Upload Album"/>
</form>
This works fine if I only have to upload one file (photo) with that form.
Update:
However what I want to do is, show minimum of three input for uploading photos to the new album:
<form action="/{{ user.username }}/create_album/" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ aform.as_p }}
{{ pform.as_p }}
{{ pform.as_p }}
{{ pform.as_p }}
<input type="submit" value="Create and Upload Album"/>
</form>
When doing so, only the last pform is gets saved. And the other two pform's are ignored. How do I get to save all the three forms of photo (pform) accordingly?
Or is there any other way around? Your help will be much appreciated! Thank you.
Use formsets. They do exactly what you want:
from django.forms.models import formset_factory
PhotoFormSet = formset_factory(AlbumPhotoForm, can_delete=False,
min_num=1, validate_min=True,
max_num=3, validate_max=True,
extra=3)
def create_album(request, user_name):
user = User.objects.get(username=unquote(user_name))
if request.method=='POST':
form = AlbumForm(request.POST)
formset = PhotoFormSet(request.POST, request.FILES)
if all([form.is_valid(), formset.is_valid()]):
album = form.save(commit=False)
album.user = user
album.save()
for photo_form in formset:
if photo_form.cleaned_data:
photo = photo_form.save(commit=False)
photo.album = album
photo.user = user
photo.save()
return redirect('/%s/photos' % user.username )
else:
form = AlbumForm()
formset = PhotoFormSet()
return render(request, 'create_album.html',
{'form': form, 'formset': formset})
And template may look like this:
<form action="." method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<table>
{{ formset }}
</table>
<input type="submit" value="Create and Upload Album"/>
</form>