I have a pretty simple site working in Flask that's all powered from an sqlite db. Each page is stored as a row in the page table, which holds stuff like the path, title, content.
The structure is hierarchical where a page can have a parent. So while for example, 'about' may be a page, there could also be 'about/something' and 'about/cakes'. So I want to create a navigation bar with links to all links that have a parent of '/' (/ is the root page). In addition, I'd like it to also show the page that is open and all parents of that page.
So for example if we were at 'about/cakes/muffins', in addition to the links that always show, we'd also see the link to 'about/cakes', in some manner like so:
- About/
- Cakes/
- Muffins
- Genoise
- Pies/
- Stuff/
- Contact
- Legal
- Etc.[/]
with trailing slashes for those pages with children, and without for those that don't.
Code:
#app.route('/')
def index():
page = query_db('select * from page where path = "/"', one=True)
return render_template('page.html', page=page, bread=[''])
#app.route('/<path>')
def page(path=None):
page = query_db('select * from page where path = "%s"' % path, one=True)
bread = Bread(path)
return render_template('page.html', page=page, crumbs=bread.links)
I already feel like I'm violating DRY for having two functions there. But doing navigation will violate it further, since I also want the navigation on things like error pages.
But I can't seem to find a particularly Flasky way to do this. Any ideas?
The "flasky" and pythonic way will be to use class-based view and templates hierarchy
First of all read documentation on both, then you can refactor your code based on this approach:
class MainPage(MethodView):
navigation=False
context={}
def prepare(self,*args,**kwargs):
if self.navigation:
self.context['navigation']={
#building navigation
#in your case based on request.args.get('page')
}
else:
self.context['navigation']=None
def dispatch_request(self, *args, **kwargs):
self.context=dict() #should nullify context on request, since Views classes objects are shared between requests
self.prepare(self,*args,**kwargs)
return super(MainPage,self).dispatch_request(*args,**kwargs)
class PageWithNavigation(MainPage):
navigation = True
class ContentPage(PageWithNavigation):
def get(self):
page={} #here you do your magic to get page data
self.context['page']=page
#self.context['bread']=bread
#self.context['something_Else']=something_Else
return render_template('page.html',**self.context)
Then you can do following:
create separate pages, for main_page.html and page_with_navigation.html
Then your every page "error.html, page.html, somethingelse.html" based on one of them.
The key is to do this dynamically:
Will modify prepare method a bit:
def prepare(self):
if self.navigation:
self.context['navigation']={
#building navigation
#in your case based on request.args.get('page')
}
else:
self.context['navigation']=None
#added another if to point on changes, but you can combine with previous one
if self.navigation:
self.context['extends_with']="templates/page_with_navigation.html"
else:
self.context['extends_with']="templates/main_page.html"
And your templates:
main_page.html
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
{% block navigation %}
{% endblock %}
{% block main_content %}
{% endblock %}
</body>
</html>
page_with_navigation.html
{% extends "/templates/main_page.html" %}
{% block navigation %}
here you build your navigation based on navigation context variable, which already passed in here
{% endblock %}
page.html or any other some_page.html. Keep it simple!
Pay attention to first line. Your view sets up which page should go in there and you can easily adjust it by setting navigation= of view-class.
{% extends extends_with %}
{% block main_content %}
So this is your end-game page.
Yo do not worry here about navigation, all this things must be set in view class and template should not worry about them
But in case you need them they still available in navigation context variable
{% endblock %}
You can do it in one function by just having multiple decorators :)
#app.route('/', defaults={'path': '/'})
#app.route('/<path>')
def page(path):
page = query_db('select * from page where path = "%s"' % path, one=True)
if path == '/':
bread = Bread(path)
crumbs = bread.links
else:
bread = ['']
crumbs = None
return render_template('page.html', page=page, bread=bread, crumbs=crumbs)
Personally I would modify the bread function to also work for the path / though.
If it's simply about adding variables to your context, than I would recommend looking at the context processors: http://flask.pocoo.org/docs/templating/#context-processors
Related
Sorry if this is an obvious question, I am new to django and still learning.
I am creating a website that contains 6 images in total. The images in the website should remain invisible until its image id is passed from views.py.
I have a template index.html page and view that loads when this url is accessed localhost/imageid
What I need now is to make the image visible whenever its url is access. So for instance if a user goes to localhost/1. QR code 1 should be made visible. I am also storing the state of all access images. So if the user accesses the website again and goes to localhost/2 it should make image 1 and 2 visible. I am using sessions to store the state. I just need a way of making the images visible.
Thankyouuuu
Depends so a basic view would be:
from django.views.generic import TemplateView
class MyView(TemplateView):
template_name = 'my_html.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['img_id'] = self.kwargs.get('pk') # this is whatever the variable is named in the URL.
return context
html would be something like this.:
<body>
{%if img_id == 1 %}
<img src="image1path">
{%elif img_id == 2 %}
<img src="image2path">
{% endif %}
</body>
A better way would be to store the images in a database and get the image paths from a model, then you would get that in the context and reference it in the template instead of having if statements.
There is a common case I encounter, where I can't find a way to split apps.
The case is when a info of two models is related and needs to be in the same template
An example speaks 1000 words: (2 models - pages + comments).
# models.py
class Page(models.Model):
title = models.CharField()
content = models.TextField()
class Comment(models.Model):
page = models.ForeignKey('Page')
content = models.TextField()
# url.py
...
url(r'^page/(?P<page_pk>\d+)/$', views.ViewPage, name='page-view-no-comments'),
url(r'^comment/(?P<comment_pk>\d+)/$', views.ViewComment, name='comment-view'),
url(r'^page-with-comments/(?P<page_pk>\d+)/$', views.ViewPageWithComments, name='page-view-with-comments'),
...
# views.py
def ViewPage(request, page_pk):
page = get_object_or_404(Page, pk=page_pk)
return render(request, 'view_page.html', {'page':page,})
def ViewComment(request, comment_pk):
comment = get_object_or_404(Comment, pk=comment_pk)
return render(request, 'view_comment.html', {'comment':comment})
def ViewPageWithComments(request, page_pk):
page = get_object_or_404(Page, pk=page_pk)
page_comments = Comment.objects.filter(page=page)
return render(request, 'view_page.html', {'page':page,'page_comments':page_comments'})
In this situation, splitting to Page app and Comment app is problematic, because they share a view (ViewPageWithComments) and url.
My options are:
1) Create an Ajax call to comments, which has crawling problems although Google might have fixed it lately.
2) Create a method of page that calls a method in the comments app that returns html with the comments content. If the method needs more arguments I also need to write a custom filter tag.
3) Decide not to split...
Am I missing something and there's another option? When would you prefer (1) vs (2) ?
Note - I created a very simple example to keep the problem general.
You don't need to split anything, you have the pages, and comments have a foreign key to that so you can just iterate over the pages comments
{% for page in pages %}
{% for comment in page.comment_set.all %}
{% endfor}
{% endfor %}
If you want to be able to use the same template for a version of this page without comments you can just wrap the comment for loop in an {% if show_comments %} statement
this is probably a question for absolute beginners since i'm fairly new to progrmaming. I've searched for couple of hours for an adequate solution, i don't know what else to do.
Following problem. I want to have a view that displays. e.g. the 5 latest entries & 5 newest to my database (just an example)
#views.py
import core.models as coremodels
class LandingView(TemplateView):
template_name = "base/index.html"
def index_filtered(request):
last_ones = coremodels.Startup.objects.all().order_by('-id')[:5]
first_ones = coremodels.Startup.objects.all().order_by('id')[:5]
return render_to_response("base/index.html",
{'last_ones': last_ones, 'first_ones' : first_ones})
Index.html shows the HTML content but not the content of the loop
#index.html
<div class="col-md-6">
<p> Chosen Items negative:</p>
{% for startup in last_ones %}
<li><p>{{ startup.title }}</p></li>
{% endfor %}
</div>
<div class="col-md-6">
<p> Chosen Items positive:</p>
{% for startup in first_ones %}
<li><p>{{ startup.title }}</p></li>
{% endfor %}
Here my problem:
How can I get the for loop to render the specific content?
I think Django show render_to_response in template comes very close to my problem, but i don't see a valid solution there.
Thank you for your help.
Chris
--
I edited my code and problem description based on the solutions provided in this thread
the call render_to_response("base/showlatest.html"... renders base/showlatest.html, not index.html.
The view responsible for rendering index.html should pass all data (last_ones and first_ones) to it.
Once you have included the template into index.html
{% include /base/showlatest.html %}
Change the view above (or create a new one or modify the existing, changing urls.py accordingly) to pass the data to it
return render_to_response("index.html",
{'last_ones': last_ones, 'first_ones' : first_ones})
The concept is that the view renders a certain template (index.html), which becomes the html page returned to the client browser.
That one is the template that should receive a certain context (data), so that it can include other reusable pieces (e.g. showlatest.html) and render them correctly.
The include command just copies the content of the specified template (showlatest.html) within the present one (index.html), as if it were typed in and part of it.
So you need to call render_to_response and pass it your data (last_ones and first_ones) in every view that is responsible for rendering a template that includes showlatest.html
Sorry for the twisted wording, some things are easier done than explained.
:)
UPDATE
Your last edit clarified you are using CBV's (Class Based Views).
Then your view should be something along the line:
class LandingView(TemplateView):
template_name = "base/index.html"
def get_context_data(self, **kwargs):
context = super(LandingView, self).get_context_data(**kwargs)
context['last_ones'] = coremodels.Startup.objects.all().order_by('-id')[:5]
context['first_ones'] = coremodels.Startup.objects.all().order_by('id')[:5]
return context
Note: personally I would avoid relying on the id set by the DB to order the records.
Instead, if you can alter the model, add a field to mark when it was created. For example
class Startup(models.Model):
...
created_on = models.DateTimeField(auto_now_add=True, editable=False)
then in your view the query can become
def get_context_data(self, **kwargs):
context = super(LandingView, self).get_context_data(**kwargs)
qs = coremodels.Startup.objects.all().order_by('created_on')
context['first_ones'] = qs[:5]
context['last_ones'] = qs[-5:]
return context
I want breadcrumbs for navigating my Flask app. An option could be to use a general Python module like bread.py:
The bread object accepts a url string and grants access to the url
crumbs (parts) or url links (list of hrefs to each crumb) .
bread.py generates the breadcrumb from the url path, but I want the elements of the breadcrumb to be the title and link of the previously visited pages.
In Flask, maybe this can be done using a decorator or by extending the #route decorator.
Is there a way to have each call of a route() add the title and link of the page (defined in the function/class decorated with #route) to the breadcrumb? Are there other ways to do it? Any examples of breadcrumbs implemented for Flask?
So you're after "path/history" breadcrumbs, rather than "location" breadcrumbs to use the terminology from the wikipedia article?
If you want to have access to the user's history of visited links, then you're going to have to save them in a session. I've had a go at creating a decorator to do this.
breadcrumb.py:
import functools
import collections
import flask
BreadCrumb = collections.namedtuple('BreadCrumb', ['path', 'title'])
def breadcrumb(view_title):
def decorator(f):
#functools.wraps(f)
def decorated_function(*args, **kwargs):
# Put title into flask.g so views have access and
# don't need to repeat it
flask.g.title = view_title
# Also put previous breadcrumbs there, ready for view to use
session_crumbs = flask.session.setdefault('crumbs', [])
flask.g.breadcrumbs = []
for path, title in session_crumbs:
flask.g.breadcrumbs.append(BreadCrumb(path, title))
# Call the view
rv = f(*args, **kwargs)
# Now add the request path and title for that view
# to the list of crumbs we store in the session.
flask.session.modified = True
session_crumbs.append((flask.request.path, view_title))
# Only keep most recent crumbs (number should be configurable)
if len(session_crumbs) > 3:
session_crumbs.pop(0)
return rv
return decorated_function
return decorator
And here's a test application that demonstrates it. Note that I've just used Flask's built-in client side session, you'd probably want to use a more secure server-side session in production, such as Flask-KVsession.
#!/usr/bin/env python
import flask
from breadcrumb import breadcrumb
app = flask.Flask(__name__)
#app.route('/')
#breadcrumb('The index page')
def index():
return flask.render_template('page.html')
#app.route('/a')
#breadcrumb('Aardvark')
def pagea():
return flask.render_template('page.html')
#app.route('/b')
#breadcrumb('Banana')
def pageb():
return flask.render_template('page.html')
#app.route('/c')
#breadcrumb('Chimp')
def pagec():
return flask.render_template('page.html')
#app.route('/d')
#breadcrumb('Donkey')
def paged():
return flask.render_template('page.html')
if __name__ == '__main__':
app.secret_key = '83cf5ca3-b1ee-41bb-b7a8-7a56c906b05f'
app.debug = True
app.run()
And here's the contents of templates/page.html:
<!DOCTYPE html>
<html>
<head><title>{{ g.title }}</title></head>
<body>
<h1>{{ g.title }}</h1>
<p>Breadcrumbs:
{% for crumb in g.breadcrumbs %}
{{ crumb.title }}
{% if not loop.last %}ยป{% endif %}
{% endfor %}
</p>
<p>What next?</p>
<ul>
<li>Aardvark?</li>
<li>Banana?</li>
<li>Chimp?</li>
<li>Donkey?</li>
</ul>
</body>
</html>
i was trying to use the breadcrumb.py , but i was need to check:
if the new item "item = (flask.request.path, view title) " is already exist in the session crumbs, then i will delete all other items frome the index to the end, i do this for Avoid repetition in my session crumds.
flask.session.modified = True
item = (flask.request.path, view_title)
try:
if not item in session_crumbs:
session_crumbs.append(item)
else:
index = session_crumbs.index(item)
session_crumbs = session_crumbs[:index+1]
except:
pass
return rv
return decorated_function
return decorator
I am using a Django Paginator and I want to have multiple available get parameters, such as:
page=1
sort_by=price
However, in my template tags I have:
Showing items sorted by {{ SORT_PARAM }}.
Showing {{ ITEMS_PER_PAGE }} items per page.
{% if has_prev %}
Previous |
{% endif %}
However, this does not preserve the other GET variables. What I mean is, if I'm viewing
page/?page=1&sort_by=price
and I click the link in the template fragment above, I will go to
page=2
instead of
page=2&sort_by=price
What I mean is, the a href does not preserve the other GET parameters.
One solution is I could type all the possible GET parameters in the a href, such as
Previous
but this will become less scalable the more arguments I want to add to my browsing. I'm guessing there should be an automated way to obtain all GET parameters, and then pass those and one more?
This one http://djangosnippets.org/snippets/1592/ looks cleaner
You can create a 'parameter-string'. Let's supose that in your code you have:
my_view( request, page, options):
sort_choices = {P:'price',N:'name', ...}
n_item_choices = {'S':5, 'L':50, 'XL':100)
ascending_descending_choices = {'A':'', 'D':'-'}
...
then you can concatenat options as:
options='P-S-D' #order by price, 5 items per page, descending order
encode opions as:
Previous
then, in urls.py capture options and in view:
my_view( request, page, options):
... #choides ....
try:
optionsArray = options.split('-')
sort_by = sort_choices[ optionsArray[0] ]
n_ites_page = n_item_choices[ optionsArray[1] ]
asc_or_desc = ascending_descending_choices[ optionsArray[2] ]
...
except:
somebody is playing ....
with this method you are free to add more paginations options without modify urls.py, all you need is to append options at the end of string options . This has advantages but also some dangers: I hope you can identify risks.
With Django's Pagination - preserving the GET params is simple.
First copy the GET params to a variable (in view):
GET_params = request.GET.copy()
and send it to the template in via context dictionary:
return render_to_response(template,
{'request': request, 'contact': contact, 'GET_params':GET_params}, context_instance=RequestContext(request))
Second thing you need to do is use it specify it in the url calls (href) in the template - an example (extending the basic pagination html to handle extra param condition):
{% if contacts.has_next %}
{% if GET_params %}
next
{% else %}
next
{% endif %}
{% endif %}
Source - Posted same answer.