All items showing before search is preformed - python

I am trying to do a basic search feature but I am having a small issue. When I go to the template that has the search form, it is displaying all the items before I even try to search. Is there a way to show a blank template until the user has put in a search term and hit the search button?
Example:
[Search field][Button]
1
2
3
etc
views.py
def view_player_home(request):
if request.method == 'GET':
form = searchPlayerForm(request.GET)
if form.is_valid():
string = form.cleaned_data.get('text')
players = Player.objects.filter(Q(first_name__icontains = string)|Q(last_name__icontains = string))
return render_to_response('player/player.html', {'form': form, 'players':players}, context_instance=RequestContext(request))
else:
form = searchPlayerForm()
return render_to_response('player/player.html', {'form': form}, context_instance=RequestContext(request))
forms.py
class searchPlayerForm(forms.Form):
text = forms.CharField(label = "Search")
def __init__(self, *args, **kwargs):
super(searchPlayerForm, self).__init__(*args, **kwargs)
self.fields['text'].required = False
template
{% extends "base.html" %}
{% block content %}
<h5>Find Player</h5>
<form method="GET" action="">
{% csrf_token %}
{{ form.as_table }}
<input type="submit" value="Submit"/>
</form>
{% if players %}
{% for p in players %}
{{ p.first_name }} {{ p.last_name }}
{% endfor %}
{% else %}
No Players
{% endif %}
{% endblock %}

One change should do it:
if request.method == 'GET':
should be
if request.GET:
The underlying issue is that your request method is always GET, so you never go into the else block or to the bottom of the function.

Another option is to explicitly look for a term in the GET data
if request.GET and 'text' in request.GET:
# do query / processing
or even don't allow blanks
if request.GET and 'text' in request.GET and request.GET['text'] != '':
# do query / processing
This work easily if you only have one field or are checking if options in a form have certain values.
When doing a lot of fields, I like to do a named submit button so that I can check is it's been hit, and then do the if statements checking for the button name.

Related

How can I implement update and delete in django view?

I am creating a movie review website. In it, I want to be able to allow a User to make one comment on one movie and then Update or Delete that comment. But I am only able to implement POST right now. How do I change the view, html or model?
Question to ask
How can I keep the comments posted by a user at the top of the comment list so that they can be updated and deleted?
An example of what we would like to implement is Rotten Tomatoes.
Models.py:
class Comment_movie(models.Model):
comment = models.TextField(max_length=1000)
stars = models.FloatField(
blank=False,
null=False,
default=0,
validators=[MinValueValidator(0.0),
MaxValueValidator(10.0)]
)
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
movie = models.ForeignKey(Movie, on_delete=models.CASCADE)
created_at = models.DateTimeField(default=datetime.now)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
unique_together = ('user', 'movie')
indexes = [
models.Index(fields=['user', 'movie']),
]
views.py:
def view_movie_detail(request, movie_id):
if not(Movie.objects.filter(id=movie_id)):
Movie(id = movie_id).save()
movie = Movie.objects.get(id=movie_id)
if request.method == "POST":
form = Comment_movie_CreateForm(request.POST)
if form.is_valid():
Comment_movie(
comment = form.cleaned_data['comment'],
user = request.user,
stars = form.cleaned_data['stars'],
movie = movie
).save()
return redirect('view_movie_detail', movie_id=movie_id)
else:
form = Comment_movie_CreateForm()
data = requests.get(f"https://api.themoviedb.org/3/movie/{movie_id}?api_key={TMDB_API_KEY}&language=en-US")
recommendations = requests.get(f"https://api.themoviedb.org/3/movie/{movie_id}/recommendations?api_key={TMDB_API_KEY}&language=en-US")
comments = reversed(Comment_movie.objects.filter(movie_id=movie_id))
average = movie.average_stars()
context = {
"data": data.json(),
"recommendations": recommendations.json(),
"type": "movie",
"comments": comments,
"average" : average,
"form": form,
}
return render(request, "Movie/movie_detail.html", context)
movie.html:
<h2>Comments</h2>
{% if form.errors %}
<div class = "error_list">
{% for errors in form.errors.values %}
{% for error in errors %}
{{ error }}<br>
{% endfor %}
{% endfor %}
</div>
{% endif %}
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form }}
<button type="submit">Post Comment</button>
</form>
{% endif %}
<hr>
Looks like you want to do multiple actions in one view. One form for each action and a template field to differentiate actions would be a solution. In this specific case, 'create' action and 'update' action can be automatically determined if we take advantage of unique_together.
from django.shortcuts import get_object_or_404
def view_movie_detail(request, movie_id):
# It makes little sense you create a movie with just an id attr
# So I use get_object_or_404 instead
movie = get_object_or_404(Movie, id=movie_id)
try:
comment_movie = Comment_movie.objects.get(user=request.user, movie=movie)
except Comment_movie.DoesNotExist:
comment_movie = None
if request.method == 'POST':
if request.POST.get('action') == 'delete':
comment_movie.delete()
return redirect('view_movie_detail', movie_id=movie_id)
else:
form = Comment_movie_CreateForm(request.POST, instance=comment_movie)
if form.is_valid():
form.save()
return redirect('view_movie_detail', movie_id=movie_id)
else:
form = Comment_movie_CreateForm(instance=comment_movie)
# Put your view logic outside of the conditional expression.
# Otherwise your code breaks when the form validates to False
data = requests.get(f"https://api.themoviedb.org/3/movie/{movie_id}?api_key={TMDB_API_KEY}&language=en-US")
recommendations = requests.get(f"https://api.themoviedb.org/3/movie/{movie_id}/recommendations?api_key={TMDB_API_KEY}&language=en-US")
comments = reversed(Comment_movie.objects.filter(movie_id=movie_id).exclude(user=request.user))
average = movie.average_stars()
context = {
"data": data.json(),
"recommendations": recommendations.json(),
"type": "movie",
"comments": comments,
"average" : average,
"form": form,
"comment_movie": comment_movie, # NOTE add the comment to context
}
return render(request, "Movie/movie_detail.html", context)
Note instance=coment_movie will make form use instance attribute when rendering in template.
And in your templates, render all three forms, and add ‘action’ to each form. One good place would be the submit button.
<h2>Comments</h2>
{% if form.errors %}
<div class = "error_list">
{% for errors in form.errors.values %}
{% for error in errors %}
{{ error }}<br>
{% endfor %}
{% endfor %}
</div>
{% endif %}
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form }}
{% if comment_movie %}
<button type="submit">Edit Comment</button>
<button type="submit" name="action" value="delete">Delete Comment</button>
{% else %}
<button type="submit">Post Comment</button>
{% endif %}
</form>
{% endif %}
<hr>
{% for comment in comments %}
<div>{{ comment.comment }}</div>
{% endfor %}
Check out django-multi-form-view. This module does not fit your question perfectly, but shares some basic ideas.
Note two addtional submit buttons in template. They are rendered only if comment is not None, which means user has made comment before. The second button coresponds to action='delete'
To your question: Render the form first, and render the rest comments after the form such that user comment is always at top.

django redirect to form view and autofill with previously entered values

I have following scenario.
User fills out a form
If the user clicks the "continue" button and the form is valid the user will be redirected to a summary view
In the summary view the user checks the input again. He can either continue or go back.
If he continues the data will be saved in the database, if he goes back he can edit the form.
Since in step 4 the user is at the view summary I have to redirect him to the home view. I don´t want the user to fill out the entire form again, the previously entered data should be autofilled if he goes back.
Something special: I am using django-tagify2 for one input in the form to get tags rather then text. If the user goes back the tags should be rendered correctly in the tagify specific form.
So here are my files:
home.html
{% extends "messenger/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="message-container">
<form method="POST" autocomplete="off">
{% csrf_token %}
{{ form|crispy }}
<button name="sendmessage" class="btn btn-outline-info" type="submit">Continue</button>
</form>
</div>
{% endblock content %}
summary.html
{% extends "messenger/base.html" %}
{% block content %}
<h4>Zusammenfassung</h4>
<p><b>Empfänger: </b>{{ receiver }}</p>
<br>
<p><b>Betreff: </b>{{ subject }}</p>
<br>
<p><b>Nachricht: </b>{{ message }}</p>
<button>Edit</button>
<button>Continue</button>
{% endblock content %}
home view
#login_required(login_url='login')
def home(request):
if request.method == 'POST' and 'sendmessage' in request.POST:
message_form = MessageForm(request.POST)
if message_form.is_valid():
receiver_list = message_form['receiver'].value().split(';')
subject = message_form['subject'].value()
message = message_form['textmessage'].value()
#create sessions and send data to next view
session_receiver = receiver_list
request.session['session_receiver'] = session_receiver
session_subject = subject
request.session['session_subject'] = session_subject
session_message = message
request.session['session_message'] = session_message
return redirect('summary')
else:
message_form = MessageForm()
return render(request, 'messenger/home.html', {'form': message_form})
summary view
def summary(request):
receiver = request.session.get('session_receiver')
subject = request.session.get('session_subject')
message = request.session.get('session_message')
return render(request, 'messenger/summary.html', {'receiver':receiver,
'subject':subject,
'message':message})
So what is the best way to do this?
Can I use the session variables to set the fields in the form?
I don´t want to change the logic in the app. I want a home/summary/success view/template where I can loop as long is I want between home and summary until the user is happy with his entered form data
How about checking request.session when there is get request to home view? Then you can bind message_form = MessageForm() to session data.
You can check out htmx and django-htmx. You can do what you want easily without session by swapping HTML with context.
I played around with the session values and views and finally got a way to redirect to other views with prefilled form fields based on session values.
#login_required(login_url='login')
def home(request):
if request.method == 'POST' and 'sendmessage' in request.POST:
message_form = MessageForm(request.POST)
if message_form.is_valid():
ad_group = message_form['ad_group'].value().split(';')
ad_user = message_form['ad_user'].value().split(';')
subject = message_form['subject'].value()
message = message_form['textmessage'].value()
#create sessions and send data to next view
session_ad_group = ad_group
request.session['session_ad_group'] = session_ad_group
session_ad_user = ad_user
request.session['session_ad_user'] = session_ad_user
session_subject = subject
request.session['session_subject'] = session_subject
session_message = message
request.session['session_message'] = session_message
return redirect('summary')
else:
if request.session.get('session_subject'):
message_form = MessageForm(initial={'ad_group': request.session.get('session_ad_group'),
'ad_user': request.session.get('session_ad_user'),
'subject': request.session.get('session_subject'),
'textmessage': request.session.get('session_message')})
return render(request, 'messenger/home.html', {'form': message_form})
else:
message_form = MessageForm()
return render(request, 'messenger/home.html', {'form': message_form})
def summary(request):
ad_group = request.session.get('session_ad_group')
ad_user = request.session.get('session_ad_user')
subject = request.session.get('session_subject')
message = request.session.get('session_message')
if request.method == 'POST' and 'edit' in request.POST:
message_form = MessageForm(initial={'ad_group':ad_group, 'ad_user': ad_user,
'subject':subject, 'textmessage':message})
return render(request, 'messenger/home.html', {'form': message_form})
return render(request, 'messenger/summary.html', {'ad_group':ad_group,
'ad_user': ad_user,
'subject':subject,
'message':message})
Template
{% extends "messenger/base.html" %}
{% block content %}
<h2>Zusammenfassung</h2>
<div class="border-top pt-3">
<p><b>AD-Gruppe: </b>{{ ad_group }}</p>
<p><b>AD-User: </b>{{ ad_user }}</p>
<br>
<p><b>Betreff: </b>{{ subject }}</p>
<br>
<p><b>Nachricht: </b>{{ message }}</p>
<div class="buttoncontainer">
<form name="edit" action="" method="post">
{% csrf_token %}
<button class="btn edit_btn" formaction="{% url 'messenger-home' %}">Zurück</button>
</form>
<form name="senden" action="" method="post">
{% csrf_token %}
<button class="btn continue_btn" formaction="{% url 'send_messages' %}">Nachricht senden</button>
</form>
</div>
</div>
{% endblock content %}

"This field is required" when all fields are filled in Django

When filling out a form I get "This field is required." even though all fields are filled in.
It doesn't have to do with setting required to False or anything like that, because all fields are required.
views.py
def upload(request):
if request.method == 'POST':
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid():
title = form.cleaned_data['title']
username = request.user.get_username()
category = form.cleaned_data['category']
handle_uploaded_file(request.FILES['file'],title,username,category)
return HttpResponseRedirect('')
else:
form = UploadFileForm()
return render(request, 'main/upload.html', {'form': form})
function
def handle_uploaded_file(f,title,username,category):
with open('/uploads/' + category + '/' + title, 'wb+') as destination:
for chunk in f.chunks():
destination.write(chunk)
forms.py
class UploadFileForm(forms.Form):
title = forms.CharField(max_length=50)
category = forms.CharField(max_length=50)
file = forms.FileField()
upload.html
{% extends 'base.html' %}
{% block title %}Upload{% endblock %}
{% block content %}
{% if user.is_authenticated %}
Uploading as: {{ user.username }}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit"/>
</form>
{% else %}
<p>You are not logged in</p>
login
{% endif %}
{% endblock %}
The error I get when filling out a form is: "This field is required"
Screenshot:
When I select a file and it throws the error it unselects whatever file I've selected, similar to how the password field is cleared when hitting sign up without completing every field.
The file isn't being submitted with the request because you didn't sent the correct enctype on the form element. Here are Django's docs concerning that.
<form method="post" enctype="multipart/form-data">
One way to verify this/debug it would be to print the form's data form.data, request.POST and/or request.FILES before the call to is_valid. Or verifying the request in a browser's dev tools.

Flask-WTForms form.validate() fails on dynamic choices

I'm creating a survey application that displays the survey question and choices and allows the user to pick a choice through the Flask-WTForms package. The form uses a RadioField and seems to fail form.validate() when populating the choices attribute dynamically.
When I manually enter in the choices as such:
class SurveyAnswerForm(FlaskForm):
answers = RadioField('Answers',
coerce=str,
choices=[('18-25', '18-25'), ('26-35', '26-35')])
form.validate() returns True and there are no errors in form.error.
When I decide to populate the choices attribute dynamically (see below), form.validate() returns False and form.error returns:
{'answers': ['Not a valid choice']}.
I've been working at this for hours and am not sure why form.validate() returns False.
forms.py:
from flask_wtf import FlaskForm
from wtforms import RadioField
class SurveyAnswerForm(FlaskForm):
answers = RadioField('Answers',
coerce=str,
choices=[])
app.py:
#app.route('/survey/<int:survey_id>/questions', methods=['GET', 'POST'])
def survey_questions(survey_id):
survey = Survey.query.filter_by(id=survey_id).first()
page = request.args.get('page', 1, type=int)
questions = SurveyQuestion.query.filter_by(survey_id=survey_id)\
.order_by(SurveyQuestion.id)\
.paginate(page, 1, True)
for question in questions.items:
question_id = question.id
choices = QuestionChoices.query\
.join(SurveyQuestion,
and_(QuestionChoices.question_id==question_id,
SurveyQuestion.survey_id==survey_id)).all()
form = SurveyAnswerForm(request.form)
form.answers.choices = [(choice.choice, choice.choice)\
for choice in choices]
if request.method =='POST' and form.validate():
print('Successful POST')
next_url = url_for('survey_questions', survey_id=survey.id,
page=questions.next_num)\
if questions.has_next else None
prev_url = url_for('survey_questions', survey_id=survey.id,
page=questions.prev_num)\
if questions.has_prev else None
return render_template('survey_question.html',
survey=survey,
questions=questions.items,
choices=choices,
form=form,
next_url=next_url, prev_url=prev_url)
survey_question.html:
{% extends "layout.html" %}
{% block body %}
<h2>{{ survey.survey_title }}</h2>
{% for question in questions %}
<h3>{{ question.question }}</h3>
{% endfor %}
<form action="{{ next_url }}" method="POST">
{{ form.csrf_token }}
{{ form.answers(name='answer') }}
{% if prev_url %}
Back
{% endif %}
{% if next_url %}
<input type="submit" value="Continue">
{% else %}
Finish
{% endif %}
</form>
{% endblock %}
The problem was submitting a POST request with pagination. if the current link is /survey/1/question?page=2 the form will submit to /submit/1/question?page=3. To remedy this, I just created a separate route for submission and handled logic there.

django ModelForm save issue with ManyToManyField

I'm new to Django and was needing some help on a view error i am getting.
I wrote a view that will display a data table of "Groups" if the request method is GET, or display a form to edit a particular "Group" if the request method is POST (item to edit is passed with POST data).
Also, if POST on an existing item, i'd like the form to be pre-populated with the data i already have in the table for that item. I've pretty much got it all down, except when i goto save an edited form, i keep getting this error:
"Cannot set values on a ManyToManyField which specifies an intermediary model"
Any help would be greatly appreciated. Also, I'm new to all this web dev stuff, so if i'm doing something completely silly or am missing a concept, please feel free to flame me accordingly. ;-)
Model
class Alias(models.Model):
def __unicode__(self):
return unicode(self.alias)
alias = models.CharField(max_length=32)
class Octet(models.Model):
def __unicode__(self):
return unicode(self.num)
num = models.IntegerField(max_length=3)
class Group(models.Model):
def __unicode__(self):
return unicode(self.name)
name = models.CharField(max_length=32) #name of the group
id = models.AutoField(primary_key=True) #primary key
octets = models.ManyToManyField(Octet, through='OctetAssignment', blank=True) #not required
aliases = models.ManyToManyField(Alias, through='AliasAssignment', blank=True) #not required
class OctetAssignment(models.Model):
octet = models.ForeignKey(Octet) #octet
group = models.ForeignKey(Group) #group that octet is assigned to
class AliasAssignment(models.Model):
alias = models.ForeignKey(Alias)
group = models.ForeignKey(Group)
View
def index(request):
if request.method == 'GET':
groups = Group.objects.all().order_by('name')
return render_to_response('groups.html',
{ 'groups': groups, },
context_instance = RequestContext(request),
)
elif request.method == "POST":
g = Group.objects.get(id=request.POST['id'])
form = GroupEditForm(instance=g)
return render_to_response('groups.html',
{ 'form': form, },
context_instance = RequestContext(request),
)
def save(request):
if request.method == "POST":
form = GroupEditForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/tradedesk/groups/') # Redirect after POST
To make it complete, here is the form template code i'm using that renders the table and edit page.
Template
<h1>Group Information</h1>
{% if groups %}
<table border=1>
{% for group in groups %}
<tr>
<td>{{group.name}}</td>
<td>{% for octet in group.octets.all %} {{octet}} {% endfor %}</td>
<td>{% for alias in group.aliases.all %} {{alias}} {% endfor %}</td>
<td>{{group.analyst}}</td>
</tr>
{% endfor %}
</table>
<br></br>
<form method="post" action="/groups/">{% csrf_token %}
<select name="id" >
{% for group in groups %}
<option value="{{group.id}}">{{group.name}}</option>
{% endfor %}
</select>
<input type="submit" value="Edit">
</form>
{% endif %}
{% if form %}
<form method="post" action="/groups/save/">{% csrf_token %}
{{form}}
<br></br>
<input type="submit" value="Save">
<form>
{% endif %}
</div>
Try to remove the intermediary models OctetAssignment and AliasAssignment. They should be used only when you want to add custom fields to them. Otherwise Django creates them and uses them transparently by itself.

Categories

Resources