Simple Pyramid app: using query params for routing to edit page - pyramid

I am trying to make a simple Pyramid app, and having a hell of a time figuring out what part of the syntax I'm missing. What's happening is I have an edit page for my model, and I can not figure out how to pass in the id of the entry I'm editing.
My view reads like this:
#view_config(route_name='action', match_param='action=edit', renderer='string')
def update(request):
this_id = request.matchdict.get('id', -1)
entry = Entry.by_id(this_id)
if not entry:
return HTTPNotFound()
form = EntryUpdateForm(request.POST, entry)
if request.method == 'POST' and form.validate():
form.populate_obj(entry)
return HTTPFound(location=request.route_url('blog', id=entry.id, slug=entry.slug))
return {'form': form, 'action': request.matchdict.get('action')}
I have created an edit template, it looks like this, and is working for the create page, which uses a different model:
{% extends "templates/layout.jinja2" %}
{% block body %}
<h2>Create a Journal Entry</h2>
<form action="" method="POST">
{% for field in form %}
{% if field.errors %}
<ul>
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
<p>{{ field.label }}: {{ field }}</p>
{% endfor %}
<p><input type="submit" name="submit" value="Submit" /></p>
</form>
{% endblock %}
And the link I have made to the template looks like:
Edit Entry
Which yields the url http://0.0.0.0:6543/journal/edit?id=1. This is new and weird to me, because I'm used to Rails, where the url would look like http://0.0.0.0:6543/journal/1/edit but poking around and reading the Pyramid blogr tutorial, this seems like how Pyramid likes to have routes. Unfortunately, it still gives me a 404. It seems like I am successful passing the id of the entry into a query string, but somehow I am not telling the edit page to be at that location.
Thanks for any help you can give.

I can't see where the problem is as this minimal example works, if you navigate your browser to localhost:8080/journal/edit?id=723
#!/usr/bin/env python
from pyramid.response import Response
from pyramid.view import view_config
from pyramid.config import Configurator
from waitress import serve
#view_config(route_name="root", renderer="string")
def root_view(request):
return "root_view", request.params
#view_config(route_name='action', match_param='action=edit', renderer="string")
def action_view(request):
return "action_view", request.params
if __name__ == '__main__':
config = Configurator()
config.add_route('root', '')
config.add_route('action', '/journal/{action}')
config.scan()
app = config.make_wsgi_app()
serve(app)
Maybe you have some other problem with your routes. Can you paste them here all? Are you sure you do not have another function named update in your view?
Aside from that, you are completely free to build your routes as you wish with Pyramid.
config.add_route('action2', '/different/edit/{id}')
config.add_route('action3', '/someother/{id}/edit')
I personally would rather use one of the schemes above than the match_param predicate...

Related

Rendering a component inside another in Nunjucks & Fractal

I'm working on a design system and just getting my head around the component way of thinking.
We're using Nunjucks and Fractal.
We have an existing component, an accordion which gets it's data from a JSON object. The props being title and content.
I've built a component, like a table of contents, which displays a list of styled links, using a simple loop, pulling in from a ToC.config.js file.
I need to include that new component within the accordion component, the ToC has a title and an array containing urls and text for each link.
At present I've just copied the HTML from the accordion's view in Fractal, but that's probably not the way to go, but visually it's as the designer requires.
I can also render the accordion correctly if I put all the links, HTML and all in the config, as a string, but that's not the way it should work either.
What I want to do, which is probably the proper way, is pull that accordion in and populate it with my ToC component.
{% render '#my-accordion', 'accordion: items' %} doesn't do what I want and I can't seem to figure out how to achieve what I need to do.
Something like so:
{% render '#my-accordion' %}
// Pass in data from ToC, somehow
{{ title: title }}
{% for item in items %}
{{ item.text etc }}
{% endfor %}
Then I would have my accordion component, where its title, links and HTML etc are those from my ToC component. Sorry for the rough pseudo above, it's more an example of what I want to do. I can't copy code to an external resource.
using render doesn't appear to be the way to go, unless I'm missing something? I can't pass the list through as a string as it has classes and aria for the current page etc and content authors will be building pages with these.
A little nudge in the right direction would be great.
You can add a render-filter that similar to include and macro.
import 'nunjucks.min.js';
var env = new nunjucks.Environment({autoescape: true});
env.addFilter('render', function (template, ctx) {
const html = env.render(template, ctx); // try-catch
return env.filters.safe(html);
});
Usage
// table.njk
<table>
{% for e in rows %}
{% 'row.njk' | render({row: e}) %}
{% endfor %}
</table>
// row.njk
<tr>
{% for c in row.cols %}
<td> {{ c.name }} </td>
{% endfor %}
</tr>

How to check routes on template?

Does anyone know how to check if a template is being accessed through a url route with the django template language?
Here is my situation: There's this template article.html, that shows all posts of a blog when accessed through blog-home url route, and that also shows only posts of a given user through user-posts url route.
This template works like that because what defines which posts will be shown are the classes in views.py.
That being said, here is what I tried to do: (I know its wrong)
{% if url == 'user-posts' %}
"Some HTML h1 tag I want do be shown only when this template is accessed via 'user-posts' url"
{% endif %}
How would be the correct way, if there's any, to write this if statement?
When Django matches a url pattern a ResolverMatch object is created and made available on the request object at request.resolver_match. This ResolverMatch object can be queried in your template to acheive what you want, the url_name attribute is the name of the url pattern that was matched
{% if request.resolver_match.url_name == 'user-posts' %}
You should create a custom templatetag,
my_app/templatetags/utils.py
from django import template
register = template.Library()
#register.simple_tag(takes_context=True)
def is_active_view(context, *view_names):
request = context.get('request')
for view_name in view_names:
if getattr(request.resolver_match, 'view_name', False) and request.resolver_match.view_name == view_name:
return True
return ''
And use it in your template this way, assuming blog-home is the url name you gave to your url :
blog-home.html
{% load utils %}
{% is_active_view 'blog-home' as display_blog_home_section %}
{% if display_blog_home_section %}
<!-- Your code -->
{% endif %}
NB : this template tag can check after multiple view names at once and support namespaced url :
{% is_active_view 'front:blog-home' 'front:blog-categories' as display %}
Here is a more optimized version of this if you are using boostrap and trying to make the menu options "active" when you are on that URL
from django import template
register = template.Library()
#register.simple_tag(takes_context=True)
def is_active_tab(context, *view_names):
request = context.get('request')
for view_name in view_names:
if getattr(request.resolver_match, 'view_name', '') == view_name:
return 'active'
return ''
And you use it like
<li class="nav-item {% is_active_tab 'games:list' %}">
<a class="nav-link" href="{% url 'games:list' %}">
{% translate "My Quizzes" %}
</a>
</li>
<li class="nav-item {% is_active_tab 'games:create' %}">
<a class="nav-link" href="{% url 'games:create' %}">
{% translate "New Game" %}
</a>
</li>
It adds an extra active classname to the class of the element if any of the view_names match the current view name. Makes the template writing much cleaner

How to add active class if current url matches the given (twig)

I want to write manually a side menu for the store on opencart, and I have a problem - how to make twig add the class "active" to the link for current page
I tried to do it like this
page about something
but it doesnt work
This is exactly what the category module does so you can copy it:
{% if child.category_id == child_id %}
- {{ child.name }}
{% else %}
- {{ child.name }}
{% endif %}
You can find the file above here:
/catalog/view/theme/default/template/extension/module/category.twig
You can find its controller here:
/catalog/controller/extension/module/category.php
They way I would do it is out the route request in by the controller phone file. This would be something like the below:
$urlroute = $this->request->get['route'];
Then in the twig file you could simply check the route variable in an IF query
{% if urlroute == "something" % }
page about something
{% else %}
page about something
{% endif %}

Dynamic nav-bar elements - passed from Flask to Jinja - inherited layout template

Environment: Python 3.6, Flask 1.02, Jinja2
Objective:
Create a dynamic menu in layout.html (which is extended by content.html)
yet the url_for of the dynamic element is frequently requires a parameter to be passed
Issue statement:
How can I pass the parameters for url_for in Jinja template when rendering the template?
I feel like I would need the syntax of str().format in Jinja..
I tried to:
1. pass each part as a separate value:
menus = [{'url': 'func_name', 'menu_title': 'title', 'param': 'param_name', 'param_val': 'param_value'}]
return render_template('content1.html', menus=menus]
in jinja I tried to call it like: (I also tried it without the plus and double-quotes)
{{ url_for(func_name), param_name+ "=" + param_val }}
During rendering it gives error of
url_for() takes 1 positional argument but 2 were given
2. tried to use the {% set var_name: passed_variable %}
Built on 1st version of menus defined on server side, I tried to set the variables within Jinja, but also failed.
menus = [{'url': 'func_name', 'menu_title': 'title', 'param': 'param_name', 'param_val': 'param_value'}]
return render_template('content1.html', menus=menus]
Jinja
{% for menu in menus %}
{% set url = menu.get('url') %}
{% set param = menu.get('param') %}
{% set value = menu.get('param_val') %}
{% url_for(url, param + "=" + value %}
Yet it also didn't work.
It feels like if I give a param for the url_for syntax (not a hard-wired string) I cannot add the parameters.
3. tried to pass whole content of url_for as a string:
menus={'url_string': " 'func_name', param_name=param_value"}
yet it fails again as url_for syntacs put the whole between apostrophes, which I wouldn't need at the end.
Some references I scanned through.
Flask context-processor
It could work if I would create another template of each nav-bar for each content page - yet with that move i could simply move the navbar into the content page. However that seems dull. Stack Overflow topic
Thus question:
How can I pass the
param_id=paramval['id']
for the url_for syntax during rendering
{{ url_for('edit_question', param_id=paramval['id']) }}
The code/structure stg like below:
layout.html
<html>
<body>
{% for menu in menus %}
{% for key, value in menu.items() %}
<a href="{{ url_for(value) }}" >
{{ key }}
</a>
{% endfor %}
{% endfor %}
{% block content %}
{% endblock %}
</body>
</html>
content1.html
{% extends 'layout.html' %}
{% block content %}
content
{% endblock %}
content2.html
{% extends 'layout.html' %}
{% block content %}
content
{% endblock %}
app.py
#app.route('/')
def index():
menus = [{'menu_title1': 'menu_func_name1'}]
return render_template('content1.html', menus=menus)
#app.route('/menu_details/<int:menu_nr>')
def show_details_of_menu(menu_nr):
menus = [{'menu_title3': 'menu_func_name3', 'menu_param_name': 'menu_param_value'}
return render_template('content2.html', menus=menus)
sorry for the Wall of text..
sigh.. after hours I just found how to construct the syntax. I hope it will help others!
During rendering:
menus = [{'url': 'func_name', 'menu_title': 'title', 'parameters': {'param1': param1_value}}]
return render_template('context.html', menus=menus]
In Jinja, I adjusted the syntax to manage cases where no parameters are needed:
{% for menu in menus %}
{% if menu.get('parameters').items()|length > 0 %}
<a href="{{ url_for(menu.get('url'), **menu.get('parameters')) }}">
{{ menu.get('menu_title') }}
</a>
{% else %}
<a href="{{ url_for(menu.get('url')) }}">
{{ menu.get('menu_title') }}
</a>
{% endif %}
{% endfor %}

How can I set selected SelectField value in Jinja

I'm working on a project using Flask and flask-wtforms, and I'm facing a problem setting selected value for Selectfield in Jinja: I tried the following but nothing worked...
#app.route('/route', methods = ['GET','POST']
def route():
form = InputForm(request.form)
# Data base connection code returns connection as con and cursor as cur.
cur.execute('SELECT field FROM Table WHERE Row = %s', (Rowvar,))
Data = cur.fetchall()
con.close()
return render_template('template.html', form = form, Data = Data)
the HTMLtemplates I tried:
Try1:
<html>
...
{% for i in Data %}
{{form.selectfield.default = i[0]}}
{% endfor %}
...
</html>
Try 2:
<html>
...
{% for i in Data %}
{% form.selectfield(default = i[0]) %}
{% endfor %}
...
</html>
Try 3 using render_field function:
<html>
...
{% for i in Data %}
{{render_field(form.selectfield, default = i[0])}}
{% endfor %}
...
</html>
I also tried using render field with (value) and (select) instead of default and none of them actually worked.
So.. can you please help me on how to set a SelectField value using Jinja the right way.
Thank you very much.

Resources