Flask redirect with data - python

I have a Flask application with a GET handler that given a recipe id as a URL parameter, it retrieves the recipe from the database and then renders it:
#app.route('/recipe/<int:id>', methods=['GET'])
def get(id):
recipe = get_recipe_from_db(id)
return render_template('recipe.html', recipe=recipe)
This results in a URL like /recipe/5. Rather than showing just the id in the URL, I would like the recipe title to be part of the resulting URL, for example recipe/5/lemon-cake. On the first request only the id is known.
I'm not sure what a neat way to do this is. So far I've come up with the following:
#app.route('/recipe/<int:id>', methods=['GET'])
def get(id):
recipe = get_recipe_from_db(id)
return redirect(url_for('get_with_title', id=id, title=urlify(recipe.title)))
#app.route('/recipe/<int:id>/<title>', methods=['GET'])
def get_with_title(id, title=None):
recipe = get_recipe_from_db(id)
return render_template('recipe.html', recipe=recipe)
This works (i.e. when user visits /recipe/5 it redirects to /recipe/5/lemon-cake) but suffers from the fact that the same recipe is retrieved from the database twice.
Is there a better way to go about this?
Note: the recipe object is large containing multiple fields and I do not want to pass it over the network unecessarily.

Option 1
The easiest solution would be to modify the URL on client side, as soon as the response is received; hence, no need for redirection and/or querying the databse twice. This could be achieved by using either history.pushState() or history.replaceState().
client side
<!DOCTYPE html>
<html>
<head>
<script>
function modify_url() {
var title = {{recipe.title|tojson}};
var id = {{recipe.id|tojson}};
window.history.pushState('', '', id + "/" + title);
}
</script>
</head>
<h2>Recipe for {{recipe.title}}</h2>
<body onload="modify_url()"></body>
</html>
server side
#app.route('/recipe/<int:id>', methods=['GET'])
def get(id):
recipe = get_recipe_from_db(id)
return render_template('recipe.html', recipe=recipe)
You may also would like to retain the get_with_title() route as well, in case users bookmark/share the URL (including the title) and want it to be accessible (otherwise, "Not Found" error would be returned when accessing it).
Option 2
If you wouldn't like to query the database every time a new request arrives - not even jsut for retrieving the title (not every single column) of a recipe entry - and you have sufficient amount of memory to hold the data, I would suggest to query the database once at startup (selecting only id and title) and create a dictionary, so that you can quickly look up for a title from the recipe id. Please note, in this way, every time an INSERT/DELETE/etc operation is performed on the table, that dictionary has to be updated accordingly. Thus, if you have such operations frequently on that table, might not be the best approach to the problem, and better keep with querying the table just for retrieving the title.
recipes = dict((row[0], row[1]) for row in result) # where row[0] is id and row[1] is title
Then in your endpoint:
#app.route('/recipe/<int:id>', methods=['GET'])
def get(id):
title = recipes.get(id)
return redirect(url_for('get_with_title', id=id, title=urlify(title)))

You can look into only changing URL with javascript, as shown here. This way, you don't do any redirects or reloading.
If you don't want to mess with javascript, let's think about backend solutions:
Is loading twice from a database significant performance overhead to be worthy of a complicated solution? If not, your solution is good.
If you really want to avoid loading the whole recipe, you can load only the title in your get method, instead of the whole object.
You can write custom get_recipe_title(id) which does (roughly) SELECT title FROM recipes WHERE id={id}.
If using SQLAlchemy, you may probably use load_only - here

If you want the 'recipe title' to appear in the URL, your solution is not suitable for that. You should do this before the 'request'. There is probably an HTML page where you submit the recipe data to list all the recipes.
Try the following in your HTML page:
<ul>
{% for recipe in recipes %}
<li><a href="/postdetail/{{recipe.id}}/{{recipe.title}}"></li>
{% endfor %}
</ul>
After that, when you click on that recipe you can also see the title.

Just pass loaded recipe as argument:
#app.route('/recipe/<int:id>', methods=['GET'])
def get(id):
recipe = get_recipe_from_db(id)
return redirect(
url_for(
'get_with_title',
id=id,
title=urlify(recipe.title),
recipe=recipe,
))
#app.route('/recipe/<int:id>/<title>', methods=['GET'])
def get_with_title(id, title=None, recipe=None):
if recipe is None:
recipe = get_recipe_from_db(id)
return render_template('recipe.html', recipe=recipe)

Related

How can I pass a client-side parameter to a server-side route without using forms?

I have a simple Flask web app. My index template has various ways of interacting with clients using javascript and HTML. I am also have a form that, upon submission, routes to another flask process and uses the request.form command to retrieve user-submitted data.
However, I want to do something a little different. I would like to initiate a Flask redirection upon javascript event but include a parameter, and not use form.
For example, my index.html file would display something like this after template rendering:
function startRedirect(parameter) {
window.location.pathname = '/myRedirect';
}
<input type="checkbox" id="sample" name="sample" onChange="startRedirect(parameter);">
And part of my Flask script would have:
#app.route('/myRedirect')
def myRedirectFunction():
# do something with the parameter here
return render_template('index.html')
I realize this can be done with using a form, but I am interested in accomplishing this task without having a form. I was thinking about somehow using request.args, but don't quite understand what to do.
You can use a dynamic route to capture a simple input and pass it to the route's function.
app.route('/myRedirect/<param>')
def myRedirectFunction(param='hello world'):
return render_template('index.html', param=param)
Using this route as a redirect, you can pass a single param (or multiple if you serialize them) that you can use to do something. From there, you can either display or you can redirect again to a common endpoint so the user does not see the param in the url.
There's no need for a form or an explicit redirect, just attach a route and some parameter to the dynamic route.
Let's say you have a model to list the departments in your company:
class Departments(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), unique=True)
Now you have a department selection page:
#app.route('/departments_home', methods=['GET'])
def departments_home():
departments = Departments.query.all()
return render_template('departments_home.html',
departments=departments)
On the frontend you might have a variety of selections, each giving a link to the same route but with a different department_id:
{% for department in departments %}
Click to go to {{ department.name }}
{% endfor %}
Now you just need another route to handle this, taking the variable department_id that was passed in the GET request:
#app.route('/load_department/<department_id>', methods=['GET'])
def load_department(department_id):
department = Departments.query.get(int(department_id))
department_data = # do stuff here with the specific department

Python NoneType error when posting a database row

I am getting a NoneType attribute error when trying to create a page on Web2Py. I am trying to load the 'show' page and I get a NoneType error for my Product_Name attribute.
Here is the database I have created:
db.define_table('products',
Field('Product_Name',requires=IS_NOT_EMPTY()),
Field('Product_Description',requires=IS_NOT_EMPTY()),
Field('Product_Review',requires=IS_NOT_EMPTY()))
Here is my controller:
def index():
form = SQLFORM(db.products).process()
rows = db(db.products).select()
return locals()
def show():
post = db.products(2)
return locals()
Here is the page I am trying to run (show):
{{extend 'layout.html'}}
<h2>{{=post.Product_Name}}</h2>
<p>Hellooooo
{{=post.Product_Description}}
{{=post.Product_Review}}
</p>
Here is a page related to my show page:
{{extend 'layout.html'}}
Current Products
<table class="table">
{{for row in rows:}}
<tr>
<td>{{=row.Product_Name}}</td>
</tr>
{{pass}}
</table>
I'm pretty sure this is caused by post being None, then the view tries to access post.Product_Description and post.Product_Review, and then you get an AttributeError.
Probably you want to change
post = db.products(2)
to
post = db.products(request.args(0, cast=int))
This would make it coherent with the links you're making in index.
db.products(2) returns the record from the db.products table whose id is 2. If no such record has yet been created (which appears to be your case), then it will instead return None. In the view, when you then have post.Product_Name, that is equivalent to None.Product_name, which raises the NoneType error you are seeing.
You probably want some logic either in the show() controller function or its associated view to handle the case where the requested product id does not exist (either redirect or show a special message in the view).
Also, as noted in this answer, because the links on the "index" page have URLs like URL('show', args=row.id) (with the db.products record ID as the first URL arg), you should retrieve the requested record in the show controller via:
post = db.products(request.args(0, cast=int))

Changing css styles from view in Django

Sorry in advance if there is an obvious answer to this, I'm still learning the ropes with Django.
I'm creating a website which has 6 pre determined subjects (not stored in DB)
english, civics, literature, language, history, bible
each subject is going to be associated with a unique color.
I've got a template for a subject.html page and a view that loads from the url appname/subject/subjectname
what I need to do is apply particular css to style the page according to the subject accessed. for example if the user goes to appname/subject/english I want the page to be "themed" to english.
I hope I've made myself clear, also I would like to know if there is a way I can add actual css code to the stylesheet and not have to change attributes one by one from the back-end.
thanks very much!
In templates you can use conditionals for add css, like this:
<div class="{% if subject=='civics' %}civic-class{% endif %}"></div>
For this, subject value should come from view.
Now, for themed page, you could use the extends tag. Let's supose:
def your_view(request):
subject # Here you get the url subject, 'how' is up to you
if subject == 'english'
template_base = '/some/html/tenplate.html'
elif subject == 'civis':
template_base = '/some/other/template.html'
... # then you return 'template_base' variable to template
Then in template:
{% extends template_base %} # at the top
Hope this helps, is the same logic if you use Class-Based views.
Django's views are not responsible for the presentation, it's the template (and css etc of course)'s reponsability. Now assuming you have the same view serving different subjects, the view obviously need to know which is the current subject (I assume from a captured part of the url passed as argument to the view), so it can easily pass this information to the template, which in turn can use it to add a subject-specific class to the body tag. Then you only have to write your css accordingly.
As an example:
# urls.py
patterns = urlpatterns('',
#...
url(r'whatever/(P?<subject>[a-z-]+>)/$', 'myviews.index', ...),
)
# myviews.py
def index(request, subject):
# do whatever
context = {
# whatever else
'subject':subject
}
return render(request, "whatever/index.html", context)
# whatever/index.html
<html>
# headers etc
<body class="something {{ subject }} etc">
# whatever here
</body>
</html>
You can do this is many ways.
In general you need to return some variable from your view to the html and depending on this variable select a style sheet, if your variable name will match you style sheet's name you can do "{{variable}}.css", if not you can use JQuery.

Django Haystack - Show results without needing a search query?

I would like to display all results which match selected facets even though a search query has not been inserted. Similar to how some shop applications work e.g. Amazon
e.g. Show all products which are "blue" and between $10-$100.
Haystack does not return any values if a search query is not specified.
Any ideas how I can get around it?
Thanks!
If anyone is still looking, there's a simple solution suggested in haystack code:
https://github.com/toastdriven/django-haystack/blob/master/haystack/forms.py#L34
class SearchForm(forms.Form):
def no_query_found(self):
"""
Determines the behavior when no query was found.
By default, no results are returned (``EmptySearchQuerySet``).
Should you want to show all results, override this method in your
own ``SearchForm`` subclass and do ``return self.searchqueryset.all()``.
"""
return EmptySearchQuerySet()
Why No Results?
I imagine you're using a search template similar to the one in the haystack getting started documentation. This view doesn't display anything if there is no query:
{% if query %}
{# Display the results #}
{% else %}
{# Show some example queries to run, maybe query syntax, something else? #}
{% endif %}
The second problem is that the default search form's search() method doesn't actually search for anything unless there's a query.
Getting Results
To get around this, I'm using a custom search form. Here's an abbreviated sample:
class CustomSearchForm(SearchForm):
...
def search(self):
# First, store the SearchQuerySet received from other processing.
sqs = super(CustomSearchForm, self).search()
if not self.is_valid():
return sqs
filts = []
# Check to see if a start_date was chosen.
if self.cleaned_data['start_date']:
filts.append(SQ(created_date__gte=self.cleaned_data['start_date']))
# Check to see if an end_date was chosen.
if self.cleaned_data['end_date']:
filts.append(SQ(created_date__lte=self.cleaned_data['end_date']))
# Etc., for any other things you add
# If we started without a query, we'd have no search
# results (which is fine, normally). However, if we
# had no query but we DID have other parameters, then
# we'd like to filter starting from everything rather
# than nothing (i.e., q='' and tags='bear' should
# return everything with a tag 'bear'.)
if len(filts) > 0 and not self.cleaned_data['q']:
sqs = SearchQuerySet().order_by('-created_date')
# Apply the filters
for filt in filts:
sqs = sqs.filter(filt)
return sqs
Also, don't forget to change the view:
{% if query or page.object_list %}
{# Display the results #}
{% else %}
{# Show some example queries to run, maybe query syntax, something else? #}
{% endif %}
Actually, the view code is a little hackish. It doesn't distinguish query-less searches with no results from search with no parameters.
Cheers!
Look at SearchQuerySet.
This should be possible if color and price has been defined in your SearchIndex:
sqs = SearchQuerySet().filter(color="blue", price__range=(10,100))
You can limit the query to certain models by adding models(Model) to the SearchQuerySet. So if you want to limit your query to the model Item use:
sqs = SearchQuerySet().filter(color="blue", price__range=(10,100)).models(Item)
Following form display all the result if not query string is present. Now you can add custom filters.
from your_app.forms import NonEmptySearchForm
url(r'^your_url$',
SearchView(template='search.html',searchqueryset=sqs,form_class=NonEmptySearchForm), name='haystack_search'),
forms.py
#Overridding because the default sqs is always none if no query string is present
class NonEmptySearchForm(SearchForm):
def search(self):
if not self.is_valid():
return self.no_query_found()
sqs = self.searchqueryset.auto_query(self.cleaned_data['q'])
if self.load_all:
sqs = sqs.load_all()
return sqs
Stumpy Joe Pete's answer is pretty spot on, but as he mentioned, the template if query or page.object_list check is a little hacked. A better way of solving this would be to create your own SearchForm which would still find something if q is empty - will not repost that - AND to customize the SearchView with something like:
class MySearchView(SearchView):
def get_query(self):
query = []
if self.form.is_valid():
for field in self.form:
if field.name in self.form.cleaned_data and self.form.cleaned_data[field.name]:
query.append(field.name+'='+str(self.form.cleaned_data[field.name]))
return ' AND '.join(query)
In most cases, you won't even be using the query value, so you could just as well do a quick check if any of the fields is set and return True or something like that.. or of course you can modify the output any way you want (I'm not even 100% sure my solution would work for all field types, but you get the idea).

How to convert tag-and-username-like text into proper links in a twitter message?

I'm writing a twitter-like note-taking web app.
In a page the latest 20 notes of the user will be listed,
and when the user scroll to the bottom of the browser window more items will be loaded and rendered.
The initial 20 notes are part of the generated html of my django template, but the other dynamically loaded items are in json format.
I want to know how do I do the tag-and-username converting consistently.
Thanks in advance.
There's a couple of pieces to consider here. On the server side, you have to be able to maintain what "chunk" of the notes list the user is on. The easiest way to do this is probably the Django paginator. It works basically by taking a QuerySet, setting a count for the number of items, then giving it the "page" number (or "chunk" number) and it returns those items.
You could do it with JSON, but it would be just as easy to do it with HTML as well. When we look at the client side part of this you'll see why.
So we can have a view "api" to handle a note "chunk" (note all my code samples here are abbreviated just for demonstration. You'd want to have error handling and all that)...
def get_notes_chunk(request, *args, **kwargs):
# Get our notes, however...
all_notes = Notes.objects.all()
# Paginate them based on the page we're on...
chunk_number = request.GET.get('c')
paginator = Paginator(all_notes, 20) # (show 20 at a time)
current_chunk = paginator.page(chunk_number)
# Render to template all that jazz
render_to_template( ... , { 'paginator':paginator, 'current_chunk':current_chunk }, ...)
Our template renders <li> tags which we'll stick into a <ul> on the client...
{% for note in current_chunk.object_list %}
<li>{{ note }}</li>
{% endfor %}
Now on the client, we need to write some javascript to handle this. It's up to you to determine on what event to fire the update, but as for how, we can use a little jQuery to handle this...
<script type="text/javascript">
var chunk_count = 1;
var notes_list_id = 'notes-list'
function load_next_chunk() {
chunk_count += 1;
$.get('{% url get_notes_chunk %}?c=' + chunk_count, function(html) {
$('#'+notes_list_id).append(html);
});
}
</script>
<body>
...
<ul id="notes-list">
<!-- Render chunk #1 here -->
</ul>
...
</body>
Some things that would probably make sense...
Refactor the rendering of the notes list into a template tag so that you can re-use it for your API and the main rendering of your page
Refactor the querying/paginating of the Notes (or whatever) model so that you can re-use it in both views
Figure out on what event the next chunk will be loaded and implement that.
The question isn't that clear... but for generating the HTML out of a Tweet take a look at twp(based on the official twitter-text-java lib):
http://github.com/BonsaiDen/twp
I'm not sure exactly what you're asking, but what's wrong with something like {{ user.get_absolute_url }}? For the tag detail URLs, it really depends on what you're looking for, but you would have to construct the url and view for that yourself.

Categories

Resources