I have a list of posts that the user is able to favorite and save to their to their account. However, I keep getting the error message above. Does anybody have any idea as to where the issue lies?
views.py:
def AircraftFavourite(request, id=None):
instance = get_object_or_404(Aircraft, id=id)
queryset = Aircraft.objects.all()
context = {'all_aircraft' : queryset}
try:
selected_aircraft = context.all_aircraft.get(pk=request.POST['context'])
except(KeyError, Aircraft.DoesNotExist):
return render(request,'aircraft.html', {
"aircraft" : instance,
"error_message" : "You did not select a valid aircraft",
})
else:
selected_aircraft.is_favorite = True
selected_aircraft.save()
return render(request,'aircraft.html', context)
urls.py
urlpatterns = [
url(r'^detail/(?P<id>\d+)/$', AircraftDetail, name='AircraftDetail'),
url(r'^(?P<id>\d+)/favourite/$', AircraftFavourite, name='AircraftFavourite'),]
aircraft.html
{% block content %}
{% for a in all_aircraft %}
<table>
<tr>
<th> {{ a.title }}
</th>
</tr>
<tr>
<td>
<form action="{% url 'AircraftFavourite' id=a.id %}" method="post">
{% csrf_token %}
<input type="submit" id="aircraft{{ forloop.counter }}" name ="aircraft" value="{{ a.id }}">
<label for="aircraft{{ forloop.counter }}"
{% if aircraft.is_favourite %}
<img src="http://i.imgur.com/b9b13Rd.png" />
{% endif %}
<input type="submit" value="Favourite"><br>
</form>
</td>
</tr>
</table>
{% endfor %}
{% endblock %}
In django templates attribute access and item access are equivalent:
template.html
#rendered with context = dict(a=dict(something=5), b=SomeObject))
<body>
{{ a.something }} vs {{ b.something }}
</body>
Both will work and django figures out whether to access the attribute or get the item.
However in Python, the attribute access with the . is not equivalent to item access with the brackets [ ].
Your script should be changed to reflect this:
try:
selected_aircraft = context['all_aircraft'].get(pk=request.POST['context'])
Or ideally just access the queryset directly, so the code better reflects what you're doing:
try:
selected_aircraft = queryset.get(pk=request.POST['context'])
Change to this:
selected_aircraft = get_object_or_404(Aircraft, id=request.POST['context'])
Related
I currently have a template set up so that it loops through all of my menu items with a button that correspond to a post request in my views.py
<td>
{% for menu_items in menu %}
{{ menu_items.Menu_name }}
{% endfor %}
</td>
<td>
{% for menu_desc in menu %}
{{ menu_desc.Menu_Desc }}
{% endfor %}
</td>
<form method="post">
{% csrf_token %}
<th><input class="btn btn-success" type="submit" value="Add To Cart" name="add">.
</th>
</form>
In my views file I have a if statement that tries to get the id of the model that was clicked.
However, i'm only able to get the Query Set and not the specific ID of the model.
def ordering(request):
latest_order = Order.objects.all()
menu = Menu.objects.all()
if 'add' in request.POST:
user_order = Order.objects.get(name='')
print(menu.id)
return render(request, 'users/ordering.html', {'title':'Ordering', 'latest_order': latest_order, 'menu':menu})
When working with queryset, whatever is returned has a primary key and not id as you would occasionally think, therefore use
item.pk instead of item.id
You cannot get the id of QuerySet, in the situation, you can treat the QuerySet like an iterable, try to print this way:
menu = Menu.objects.all()
print([item.id for item in menu])
I have a Flask template that displays a page with a dropdown list of owners, a table with the owner's win-loss record, and a radio to toggle between regular season record and playoff record.
The desired workflow is:
If navigated to the page through the navigation bar, it should default to /matchup-history/regular. (this works)
Else it should route accordingly whenever the radio is toggled. (this doesn't work)
matchup-history.html
{%- extends "base.html" -%}
{% block nav_matchups %}active{% endblock %}
{%- block content -%}
<form action="{{ url_for('show_matchup_history', matchup_type=request.form['matchup_type']) }}" method="post">
<label>
<select name="owner_id" onchange="this.form.submit()">
{%- for o in owners %}
{%- if request.form['owner_id'] == o['owner_id']|string() %}
<option value="{{ o['owner_id'] }}" selected>{{o['first_name'] + " " + o['last_name'] }}</option>
{%- else %}
<option value="{{ o['owner_id'] }}">{{o['first_name'] + " " + o['last_name'] }}</option>
{%- endif %}
{%- endfor %}
</select>
</label>
{% block matchup_type_radio %}{% endblock %}
</form>
{%- if records|length > 0 %}
<div class="stats-table">
<table>
<tr>
{%- for th in table_headers %}
<th>{{ th }}</th>
{%- endfor %}
</tr>
{%- for r in records %}
<tr>
{%- for cn in column_names %}
<td>{{ r[cn] }}</td>
{%- endfor %}
</tr>
{%- endfor %}
</table>
</div>
{%- endif %}
{% endblock -%}
matchup-history/regular.html
{%- extends "matchup-history.html" -%}
{% block matchup_type_radio %}
<label><input type="radio" name="matchup_type" value="regular" onclick="this.form.submit()" checked>Regular Season</label>
<label><input type="radio" name="matchup_type" value="playoffs" onclick="this.form.submit()">Playoffs</label>
{% endblock %}
matchup-history/playoffs.html
{%- extends "matchup-history.html" -%}
{% block matchup_type_radio %}
<label><input type="radio" name="matchup_type" value="regular" onclick="this.form.submit()">Regular Season</label>
<label><input type="radio" name="matchup_type" value="playoffs" onclick="this.form.submit()" checked>Playoffs</label>
{% endblock %}
app.py
#app.route('/matchup-history/<string:matchup_type>', methods=['GET', 'POST'])
def show_matchup_history(matchup_type):
table_headers = ["Opponent", "Wins", "Losses"]
column_names = ["opponent_owner_name", "wins", "losses"]
owners = queries.get_owners()
if request.method == 'POST':
owner_id = request.form['owner_id']
else:
owner_id = owners[0]['owner_id']
if matchup_type == REGULAR_SEASON:
records = queries.get_matchup_history_regular(owner_id)
else:
records = queries.get_matchup_history_playoffs(owner_id)
return render_template("matchup-history/{matchup_type}.html".format(matchup_type=matchup_type),
title='Matchup History', table_headers=table_headers, column_names=column_names,
owners=owners, records=records)
The page correctly loads /matchup-history/regular when clicked on, but fails whenever the radio button is toggled:
127.0.0.1 - - [20/Sep/2018 08:32:53] "GET /matchup-history/regular HTTP/1.1" 200 -
127.0.0.1 - - [20/Sep/2018 08:32:56] "POST /matchup-history/ HTTP/1.1" 404 -
It seems like request.form['matchup_type'] is empty when matchup-history.html is rendered, so submitting the form will not have the desired effect. How can I refactor to route url_for to different matchup_type?
Edit: Per #Joost's suggestion, I rethought the design.
matchup-history.html
{%- extends "base.html" -%}
{% block nav_matchups %}active{% endblock %}
{%- block content -%}
<form action="{{ url_for('show_matchup_history') }}" method="get">
<label>
<select name="owner_id" onchange="this.form.submit()">
{%- for o in owners %}
<option value="{{ o['owner_id'] }}" {%- if o['owner_id'] == selected_owner %} selected {% endif %}>{{o['first_name'] + " " + o['last_name'] }}</option>
{%- endfor %}
</select>
</label>
<label><input type="radio" name="matchup_type" value="regular" onclick="this.form.submit()" {%- if matchup_type == "regular" %} checked {% endif %}>Regular Season</label>
<label><input type="radio" name="matchup_type" value="playoffs" onclick="this.form.submit()"{%- if matchup_type == "playoffs" %} checked {% endif %}>Playoffs</label>
</form>
{%- if records|length > 0 %}
<div class="stats-table">
<table>
<tr>
{%- for th in table_headers %}
<th>{{ th }}</th>
{%- endfor %}
</tr>
{%- for r in records %}
<tr>
{%- for cn in column_names %}
<td>{{ r[cn] }}</td>
{%- endfor %}
</tr>
{%- endfor %}
</table>
</div>
{%- endif %}
{% endblock -%}
base.html
...
Matchups
...
app.py
#app.route('/matchup-history', methods=['GET'])
def show_matchup_history():
table_headers = ["Opponent", "Wins", "Losses"]
column_names = ["opponent_owner_name", "wins", "losses"]
matchup_type = request.args.get('matchup_type', default="regular")
owner_id = request.args.get('owner_id', type=int)
owners = queries.get_owners()
if not owner_id:
owner_id = owners[0]['owner_id']
if matchup_type == REGULAR_SEASON:
records = queries.get_matchup_history_regular(owner_id)
else:
records = queries.get_matchup_history_playoffs(owner_id)
return render_template("matchup-history.html".format(matchup_type=matchup_type),
title='Matchup History', table_headers=table_headers, column_names=column_names,
matchup_type=matchup_type, selected_owner=owner_id, owners=owners, records=records)
Flow is now:
Clicking on Matchups from navbar will route to /matchup-history and default to showing regular season matchups
Clicking on Playoffs radio will route to /matchup-history?matchup_type=playoffs&owner_id=12345
Clicking on Regular radio will route to /matchup-history?matchup_type=regular&owner_id=12345
Clicking on a different owner in the dropdown will route to /matchup-history?matchup_type=regular&owner_id=98765
So right now you are trying to access the request.form in a get request. However, the form will always be empty in a get request because that's the nature of a get request. So only when you access the route #app.route('/matchup-history/<string:matchup_type>' trough a post request, will it be able to redirect in the right way.
This working miniapp displays it nicely:
from flask import Flask, render_template_string, request
app = Flask(__name__)
TEMPLATE_STRING = """
<form action="{{ url_for('index') }}" method="post">
{{request.form['matchup_type']}}<br><br>
<label><input type="radio" name="matchup_type" value="regular" onclick="this.form.submit()" checked>Regular Season</label>
<label><input type="radio" name="matchup_type" value="playoffs" onclick="this.form.submit()">Playoffs</label>
</form>
"""
#app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'GET':
return render_template_string(TEMPLATE_STRING)
else:
return render_template_string(TEMPLATE_STRING)
The first time you open the page, you see only the radio button. But as soon as you click on a radio button, it POSTS the form, and therefore you will see the selected value on top of the page now. If you click again, you post the form again, etc.
So how should you solve it? I think that there is no need to do a POST request with this form, because you're not updating any data, you're just querying.
from flask import Flask, render_template_string, request
app = Flask(__name__)
TEMPLATE_STRING = """
<form action="{{ url_for('history') }}" method="get">
<select name="owner_id">
{% for owner in owners %}
<option {% if owner['id'] == selected_owner_id %} selected {% endif %}value="{{owner['id']}}">{{owner['name']}}</option>
{% endfor %}
</select>
<label><input type="radio" name="matchup_type" value="regular" {%if selected_matchup_type == 'regular'%}checked{%endif%} onclick="this.form.submit()">Regular Season</label>
<label><input type="radio" name="matchup_type" value="playoffs" {%if selected_matchup_type == 'playoffs'%}checked{%endif%} onclick="this.form.submit()" >Playoffs</label>
<br>Queried data goes here
</form>
"""
owners = [{'id': 1, 'name': 'bob'}, {'id': 2, 'name': 'gary'}, {'id': 3, 'name': 'tom'}]
matchup_types = 'regular', 'playoffs'
#app.route('/history', methods=['GET'])
def history():
owner_id = request.args.get('owner_id', None, type=int)
if owner_id not in [owner['id'] for owner in owners]:
owner_id = owners[0]['id']
matchup_type = request.args.get('matchup_type', None)
if matchup_type not in matchup_types:
matchup_type = matchup_types[0]
# now you know the owner_id and the matchup type, and know that both are valid, do some query to get table data
return render_template_string(TEMPLATE_STRING, owners=owners,
selected_owner_id=owner_id,
selected_matchup_type=matchup_type,
matchup_types=matchup_types)
I think this is what you need. The form is never posted, by always put as a get request (<form action="{{ url_for('history') }}" method="get">). If the values are missing or invalid, we default back to some owner/matchup_type. The checked values are remembered, and used to render the template.
This puts all you flask logic in the #app.route, and all your jinja logic in the template.
Some general remarks:
I think that accessing the request in jinja not preferable because jinja deals with errors/missing values differently, and if they are a result of logic related to your request, it's becomes hard to guess what's going on. So deal with the incoming request on the python side.
Instead of wrapping having 2 radio blocks depending on the chosen value, just use one block and check within the options if the are to one you need. <option {% if some_value == some_other_value %} checked {% endif%}>blabla</option>.
Do a lot more input validation! In your first example, you template name is decided by some user inputted value (the matchup type). But what if the user posts a nonexisting value? You get errors.
If the only difference between the two templates is which radio button is selected, you don't need two templates. See the updated version how to just deal with it in one template.
I am working with inlineformset_factory and created a forms.BaseInlineFormSet to override the clean function to make some validations. the validation works and the form isn't accepted but the ValidationError message doesn't appear when the page is reloaded.
here is my form class:
class BaseDetailFormSet(forms.BaseInlineFormSet):
def clean(self):
super(BaseDetailFormSet, self).clean()
if any(self.errors):
return self.errors
for form in self.forms:
product = form.cleaned_data['product']
if form.cleaned_data['quantity_sold'] > product.quantity_in_stock:
raise forms.ValidationError(_('not enough products'))
DetailFormset = inlineformset_factory(Invoices,
InvoiceDetail,
fields=('product', 'quantity_sold'),
widgets= {'product': forms.Select(
attrs={
'class': 'search',
'data-live-search': 'true'
})},
formset=BaseDetailFormSet,
extra=1)
and my html code:
<form class="form-inline" method="post" action="">
{% csrf_token%}
{% include '_layouts/form_snippet.html' %}
<table class="table">
{{inlines.management_form}}
{%for form in inlines.forms%}
{% if forloop.first%}
<thead>
<tr>
{%for field in form.visible_fields%}
<th>{{field.label|capfirst}}</th>
{%endfor%}
{%endif%}
</tr>
</thead>
<tr class="formset_row">
{{ form.non_form_errors }}
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<button type="submit" value="Add" class="btn btn-inverse">{% trans 'Submit Invoice' %}</button>
</form>
any ideas how to show the validation error message or why it isn't shown.
non_form_errors belongs to the formset not the form.
So you can display it with:
{{ formset.non_form_errors }}
Or in your case, since you use the variable inlines:
{{ inlines.non_form_errors }}
Since it belongs to the formset, you should do this outside of the forloop through the forms.
If you have errors on the form that don't belong to any field, then you access these with
{{ form.non_field_errors }}
I am currently trying to compare the product id to the id given in the URL. But the if statement in the template always returns "else" even though testing provides both to be equal.
views.py (where data is given)
def editstatus(request, product_id):
try:
request.session['user_id']
except KeyError:
return redirect("/login")
products = Product.objects.all()
context = {
"product":products,
"theid" : product_id,
}
return render(request, 'posystem/status.html', context)
status.html (with not working if statement)
{%for product in product%}
<tbody>
<tr>
<td>{{product.id}}</td>
<td>{{theid}}</td>
<td>{{product.product_description}}</td>
<td>{{product.product_number}}</td>
<td>{{product.product_quantity}}</td>
<td>{{product.unit_cost}}</td>
<td>{{product.final_cost}}</td>
<td>{{product.status}}</td>
{% ifequal product.id theid %}
<h1>hello</h1>
{% else %}
<h1>hello2</h1>
{% endifequal %}
{% if theid %}
{% if product.id == theid %}
<td><select>
<option value="5 Votes Needed">5 Votes Needed</option>
<option value="Ready to Order">Ready to Order</option>
<option value="Needs to Be Signed">Needs to Be Signed</option>
<option value="Ordered">Ordered</option>
<option value="Recieved">Recieved</option>
</select></td>
<td><form class="" action="/changestatus/{{product.id}}" method="post">
{% csrf_token %}
<button type="submit" name="edit">Save</button>
</form></td>
{% endif %}
{% else %}
<td><form class="" action="/status/{{product.id}}" method="post">
{% csrf_token %}
<button type="submit" name="edit">Edit</button>
</form></td>
{% endif %}
</tr>
</tbody>
{% endfor %}
I am confused on why it will neither work with a ifequal tag nor a normal if tag.
Since product_id is from the URL then it will be a string, not an integer. You need to convert it to an integer.
context = {
"product":products,
"theid" : int(product_id),
}
In Python, and the Django template language, '1' is not equal to 1.
I am using django-filter to search a model. Here is the code:
filters.py:
class PersonFilter(django_filters.FilterSet):
lastName = django_filters.CharFilter(lookup_expr='icontains')
firstName = django_filters.CharFilter(lookup_expr='icontains')
class Meta:
model = Person
fields = ['lastName', 'firstName']
views.py:
def search(request):
people = Person.objects.all()
people = PersonFilter(request.GET, queryset=people)
context = {'filter': people}
return render(request, 'myapp/template.html', context)
template.html:
<form method="get">
{{ filter.form.as_p }}
<button type="submit">Search</button>
</form>
<table>
{% for field in filter.qs %}
<tr>
<td>
{{ field.idno }}
</td>
<td>
{{ field.lastName }}
</td>
<td>
{{ field.firstName }}
</td>
<td>
{{ field.status }}
</td>
</tr>
{% endfor %}
</table>
{% endblock %}
</body>
</html>
Right now, this results in a table that mirrors my model with search boxes for first name and last name. The search works perfectly.
How do I prevent the table of data from showing up initially? Logically, this could be done superficially (hide) or, better yet, substantially (dynamically create queryset). Is this possible?
You can leverage the FilterSet's is_bound property, although you would need to change the view code to only provide the request query params when the form has been submitted.
def search(request):
submitted = 'submitted' in request.GET
data = request.GET if submitted else None
people = PersonFilter(data, queryset=Person.objects.all())
return render(request, 'myapp/template.html', {'filter': people})
<form method="get">
{{ filter.form.as_p }}
<button type="submit" name="submitted">Search</button>
<!-- ^^^^ added 'name' parameter -->
</form>
{% if filter.is_bound %}
<table>
{% for person in filter.qs %}
...