I'm using Flask to expose a local directory of HTML files on a web page.
I am also using a jinja2 to generate the sitemap in the lefthand div of my main endpoint.
I am unable to correctly specify the URL to the endpoint of my subfolders.
As mentioned in the code below, how would I dynamically build a relative link from /docs (i.e. /docs/folder1/subfolder1/SubFolder1Page.html)?
The way I am currently setting the value for href obviously does not work.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Docs Demo</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
<div id="container">
<div class="left_frame">
<h1>{{ tree.name }}</h1>
<ul>
{%- for item in tree.children recursive %}
<!-- How would I build a relative link from /docs/ i.e. /docs/folder1/subfolder1/SubFolder1Page.html -->
<li><a href="docs/{{ item.name }}" target="iframe1">{{ item.name }}
{%- if item.children -%}
<ul>{{ loop(item.children) }}</ul>
{%- endif %}</a></li>
{%- endfor %}
</ul>
</div>
<div class="right_frame">
<iframe name="iframe1"></iframe>
</div>
</div>
</body>
</html>
Folder structure example:
How it looks overall displaying the contents of file1.html:
So I figured out a satisfying way of solving my own issue.
I managed to get this very functional result:
Do note that my template is only good for files with .html extension, though it can be easily enhanced to support other file extensions.
Here is my finalized templates\template.html file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Docs Demo</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
<div id="container">
<div class="left_frame">
<h1>{{ tree.name }}</h1>
<ul>
{%- for item in tree.children recursive %}
{% if '.html' in item.name %}
<li><a href="docs/{{ item.name }}" target="iframe1">
{{ item.name.split('/')[-1:][0] }}
{%- if item.children -%}
<ul>{{ loop(item.children) }}</ul>
{%- endif %}</a></li>
{% else %}
<li>{{ item.name }}
{%- if item.children -%}
<ul>{{ loop(item.children) }}</ul>
{%- endif %}</li>
{% endif %}
{%- endfor %}
</ul>
</div>
<div class="right_frame">
<iframe name="iframe1"></iframe>
</div>
</div>
</body>
</html>
You can refer to King Reload's answer for an analysis of what I've changed in the template.html file to make this work correctly.
And here is the demo_app.py script that serves my document HTML files via Flask:
import threading
import os
import webbrowser
from flask import Flask, render_template, send_from_directory
app = Flask(__name__, static_folder='static')
ROOT = os.path.dirname(os.path.abspath(__file__))
DOCS_ROOT = os.path.join(app.static_folder, 'docs')
#app.route('/')
def docs_tree():
return render_template('template.html', tree=make_tree(DOCS_ROOT))
#app.route('/docs/<path:filename>')
def send_docs(filename):
return send_from_directory(directory=DOCS_ROOT, 'docs'), filename=filename)
def make_tree(path):
tree = dict(name=os.path.basename(path), children=[])
try:
lst = os.listdir(path)
except OSError:
pass # ignore errors
else:
for name in lst:
fn = os.path.join(path, name)
if os.path.isdir(fn):
tree['children'].append(make_tree(fn))
else:
np = os.path.join(path.replace(DOCS_ROOT, ''), name).replace('\\', '/')
if np.startswith('/'):
np = np[1:]
tree['children'].append(dict(name=np))
return tree
if __name__ == '__main__':
host = 'localhost'
port = '8888'
url = 'http://{h}:{p}'.format(h=host, p=port)
threading.Timer(3, lambda: webbrowser.open(url)).start()
app.run(host=host, port=port, debug=False)
Most notable changes in demo_app.py since asking my original question were the following:
After initializing app, I set DOCS_ROOT using app.static_folder;
In the function send_docs(), I changed the send_from_directory()'s directory argument to use DOCS_ROOT;
Inside of make_tree(), inside the else block of the for loop, I added:
np = os.path.join(path.replace(DOCS_ROOT, ''), name).replace('\\', '/')
if np.startswith('/'):
np = np[1:]
All this does is take the absolute path of name, remove what matches DOCS_ROOT, leaving only the relative path (and then replacing the \\ for /), resulting in a simple relative path from static/docs. If the relative path starts with a /, I remove it (since there is a trailing / from docs in template.html.
For anyone interested in the simplistic stylesheet (static\styles.css) I used (along with some updated enhancements):
html {
min-height:100%;
position:relative;
}
body {
overflow:hidden;
}
.container {
width:100%;
overflow:auto;
}
.left_frame {
float:left;
background:#E8F1F5;
width:25%;
height:100vh;
}
.right_frame {
float:right;
background:#FAFAFA;
width:75%;
height:100vh;
}
.right_frame iframe {
display:block;
width:100%;
height:100%;
border:none;
}
To add onto the solution of #HEADLESS_0NE:
He added a few more if statements in the for loop, like so:
{%- for item in tree.children recursive %}
-> {% if '.html' in item.name %}
<li><a href="docs/{{ item.name }}" target="iframe1">
-> {{ item.name.split('/')[-1:][0] }}
{%- if item.children -%}
<ul>{{ loop(item.children) }}</ul>
{%- endif %}</a></li>
-> {% else %}
-> <li>{{ item.name }}
-> {%- if item.children -%}
-> <ul>{{ loop(item.children) }}</ul>
-> {%- endif %}</li>
-> {% endif %}
{%- endfor %}
Everything with an -> has been changed in the html, I couldn't find what he added in his python or css, but in short:
An if to check if there's a .html in the item.name.
A split on the item.name, so the / gets removed.
An else statement if there's no .html in the item.name.
This basically adds the ul's and the li's in the correct format.
For a more detailed explanation I hope HEADLESS_0NE could provide us of more information what he might've changed in the python script.
With flask, I have built a site map using the following
from flask import url_for
def get_flask_resources():
verbs = ["POST","GET","PUT","DELETE"]
resources = {}
for rule in app.url_map.iter_rules():
if has_no_empty_params(rule):
resource = url_for(rule.endpoint, **(rule.defaults or {}))
if resource not in resources:
resources[resource] = {}
for verb in verbs:
if verb in rule.methods:
resources[resource][verb] = {
'function':rule.endpoint,
'docs':app.view_functions[rule.endpoint].__doc__
}
else:
resource = rule.rule
if resource not in resources:
resources[resource] = {}
for verb in verbs:
if verb in rule.methods:
resources[resource][verb] = {
'function':rule.endpoint,
'docs':app.view_functions[rule.endpoint].__doc__
}
return resources
This function returns a dictionary like this
{
"/endpoint1": {
"GET": {
"docs": "",
"function": "endpoint1"
}
},
"/endpoint2": {
"GET": {
"docs": "",
"function": "endpoint2"
}
},
"/endpoint1/something": {
"POST": {
"docs": "",
"function": "endpoint1_something"
}
},
}
I had an endpoint return this data and then formatted it on the front end. The dictionary keys are the URIs that you would want to use in the links.
This does assume that you will have a flask route set up for each HTML document, which may not be the case.
One benefit of using this would be that it is dynamic if you add any more HTML documents/Flask routes.
Related
I'm using a database to retrieve project numbers and put them in a search for a user in a form. I am using an api (ajax google api) for autocompletion on the search.
When putting it as autocomplete in search field it contains brackets and a comma.
I need it just to be like:
24403
24429
not like:
(24403,)
(24429,)
I am unsure how to do this. My current code is:
app.py
class Element(db.Model):
__tablename__ = 'elements'
ProjectNumber = db.Column(db.Integer, primary_key=True)
#app.route("/", methods=["POST", "GET"])
def basicTemplate():
project_number = Element.query.with_entities(Element.ProjectNumber).distinct()
if request.method == "GET":
return render_template('search.html', navigation=project_number)
if __name__ == '__main__':
app.run()
base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/static\style.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.js"></script>
<link href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/themes/ui-lightness/jquery-ui.css" rel="stylesheet" type="text/css" />
{% block head %}
<title>{% block title %}{% endblock %}- base</title>
{% endblock %}
</head>
<body>
{% block body %}
{% block project_number %} {% endblock %}
{% endblock %}
</body>
</html>
search.html
{% extends 'base.html' %}
{% block head %}
{% endblock %}
{% block project_number %}
<label for="project_code">Project Number</label><input type="text" id="project_code" name="project_code">
<input type="submit">
<script>
$( function() {
var project = [
{% for test in navigation %}
"{{test}}",
{% endfor %}
];
$( "#project_code" ).autocomplete({
source: project
});
} );
</script>
{% endblock %}
The command with_entities always returns a list of tuples.
You can combine multiple jinja filters to get a list of strings that can be passed directly to javascript.
First you extract the first element of all tuples and convert them into strings. You convert the received generator into a list and output it in a javascript-compatible manner.
$(function() {
const projects = {{ navigation | map('first') | map('string') | list | tojson }};
$('#project_code').autocomplete({
source: projects
});
});
Of course you can also perform the extraction and conversion to the string in the endpoint.
Depending on how many projects are stored in your database, I recommend getting the data via ajax.
$(function() {
$('#project_code').autocomplete({
source: '/search',
});
});
#app.route('/search')
def search():
term = request.args.get('term', '')
projects = Element.query.filter(Element.ProjectNumber.ilike(f'%{term}%')).all()
return jsonify([str(p.ProjectNumber) for p in projects])
I am creating a website where a user can upload documents. If user is logged out he can view other's document but not download it
if user is logged in he can download the document from the toolbar in pdf viewer
Even when I am logged out request.user.is_authenticated is false in home page but its true in other pages. So the download button does not hide nor a login button shows up.
I have tried using user.is_authenticated but it was returning true all the time
the if else condition works fine in the index page but not in doc_detail.html
urls.py
re_path(r'^all_files/(?P<doc_id>[0-9]+)/$',views.doc_detail,name = 'doc_detail'),
path('login/',auth_views.LoginView.as_view(template_name='homework/login.html'),name = 'login'),
views.py
def doc_detail(request,doc_id):
template = loader.get_template('homework/doc_detail.html')
doc = get_object_or_404(Document, pk = doc_id)
context = {
'doc':doc
}
return HttpResponse(template.render(context,request))
doc_detail.html
{{ doc.user.username }}
{{ doc.title }}
{{ doc.doc_type }} <br>
{% if request.user.is_authenticated %}
<iframe src="{{ doc.document.url }}#toolbar=1&navpanes=0&scrollbar=1" type="application/pdf" width = "80%" height = "600px" />
{% else %}
<iframe src="{{ doc.document.url }}#toolbar=0&navpanes=0&scrollbar=0" type="application/pdf" width = "80%" height = "600px" />
login
{% endif %}
index page
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
This is the homework page implement upload-routine explore-routine here
{% if request.user.is_authenticated %}
the user is {{ request.user.email }} {{ request.user.profile.institution }}
{% else %}
user not logged in
{% endif %}
{% if request.user.is_authenticated %}
logout
update
upload
my uploads<br>
{% else %}
login<br>
{% endif %}
all files
</body>
</html>
As pointed out in the comments, you should be using the render shortcut; it seems you are confusing that with the render method of a template. There's no reason to be using the template object directly here, or just about anywhere in standard Django code.
def doc_detail(request, doc_id):
doc = get_object_or_404(Document, pk=doc_id)
context = {
'doc':doc
}
return render(request, 'homework/doc_detail.html', context)
views.py
#login_required
def doc_detail(request,doc_id):
template = loader.get_template('homework/doc_detail.html')
doc = get_object_or_404(Document, pk = doc_id)
context = {
'doc':doc
}
return HttpResponse(template.render(context,request))
doc_detail.html
{% if user.is_authenticated %}
<iframe src="{{ doc.document.url }}#toolbar=1&navpanes=0&scrollbar=1" type="application/pdf" width = "80%" height = "600px" />
{% else %}
<iframe src="{{ doc.document.url }}#toolbar=0&navpanes=0&scrollbar=0" type="application/pdf" width = "80%" height = "600px" />
login
{% endif %}
I have a static homepage, but I'm also using the i18n subsites plugin. So for the homepage I have, in pelicanconf.py:
INDEX_SAVE_AS = 'blog/index.html'
INDEX_URL = 'blog'
and for the English version:
I18N_SUBSITES = {
'en': {
'OUTPUT_PATH': 'output/en/',
'INDEX_SAVE_AS': 'blog/index.html',
'INDEX_URL': 'blog',
}
}
(truncated unnecessary bits)
The problem lies with the translation link for the homepage. The translations macro has:
{% for translation in article.translations %}
{{ translation.lang | lookup_lang_name }}
So for the English-language homepage I could either set the url and output filename as:
<meta name="save_as" content="en/index.html">
<meta name="url" content="en/">
Which makes the translation link go to site.com/en/en/ (and works), or set them as:
<meta name="save_as" content="index.html">
<meta name="url" content="/">
Which conflicts with the standard-language homepage.
Another, related problem is that the index page (blog page) has no translation link to the English or back to the standard-language version whatsoever.
What can I do to solve this?
I was able to solve my problem with the following translations macro:
{% macro translations_for(article) %}
{% if extra_siteurls %}
{% for lang, url in extra_siteurls.items() %}
{% if article %}
{{ lang | lookup_lang_name }}
{% else %}
{{ lang | lookup_lang_name }}
{% endif %}
{% endfor %}
{% endif %}
{% endmacro %}
And by adding a permalink option the each article and page I can also translate the URL for said article or page (otherwise you could just use slug in the above macro).
Last but not least I also removed the url and save_as data from both homepages.
To fix the blog page, I added this to the index.html template:
{% block translation_links %}
{% if lang == 'nl' %}
English
{% else %}
Nederlands
{% endif %}
{% endblock %}
For the second part of your question, have a look at creating language buttons
I am working on a django website which includes a form for contact information and I am currently using bootstrap to make it "look pretty". I want to use bootstrapvalidator http://bootstrapvalidator.com/ to make the form easy to use for my users. I have heavily researched using django and bootstrapvalidator together and have found several different plugins or other ways to validate information entered into a form but they only give errors after the form is submitted, and what I want is live validation that shows the user appropriate messages as they type.
My biggest problem has been figuring out how/where to call the Javascript. I think the correct way to do this is by using a widget in the form, but nothing I have tried has done the job. Currently my page is displaying "emailval(parent_or_guardian_email)" where the email field should be.
Would anyone be able to tell me what I'm doing wrong or how to get my desired result? This is my first time using django or python at all.
relevant parts of my models.py
from django.db import models as db_models
from django.db import models
from django.contrib.auth.models import User
from django import forms
from django.contrib.admin import widgets
from django.forms import extras
import datetime
from django.core.validators import *
from django.forms import ModelForm
from django.contrib import admin
import logging
class EmailWidget(forms.TextInput):
def render(self, name, value, attrs=None):
out = super(EmailWidget,self).render(name, value, attrs=attrs)
return out + '<script type="text/javascript">emailval(parent_or_guardian_email) </script>'
class Media:
js = ('../validator.js')
class PersonForm(forms.Form):
ready_to_play = forms.BooleanField(initial = True )
first_name = forms.CharField(max_length=35)
last_name = forms.CharField(max_length=35)
phone_number = forms.CharField(widget =forms.TextInput(attrs={'type':'text', 'class':'form-control bfh-phone','data-format':"+1 (ddd) ddd-dddd",}), required = False)
grade = forms.IntegerField(initial = 99, required = False)
age = forms.IntegerField(initial = 99, required = False)
gender = forms.ChoiceField(choices=GENDERS, required = False)
sport = forms.ChoiceField(choices=SPORTS, required = False)
#fields for parent_or_guardian and physician not autofilled
parent_or_guardian = forms.CharField(max_length=35, required = False)
parent_or_guardian_phone_number = forms.CharField(widget =forms.TextInput(attrs={'type':'text', 'class':'form-control bfh-phone','data-format':"+1 (ddd) ddd-dddd",}), required = False)
parent_or_guardian_email = forms.EmailField(widget =EmailWidget(),help_text = "Changing this field will create a new user for this email address, if you wish to change this user's contact information"
+" please ask them to do so on their page or contact a site admin ")
physician = forms.CharField(max_length=35, required = False)
physician_phone_number = forms.CharField(widget =forms.TextInput(attrs={'type':'text', 'class':'form-control bfh-phone','data-format':"+1 (ddd) ddd-dddd",}), required = False)
#Pphone = copy.deepcopy(physician.phone)
#Pphone =str(physician_phone)
#physician_phone = re.sub('.','-',str(Pphone))
physician_email = forms.EmailField(max_length=75, required = False)
in my base.html I have the appropriate file imports
<link href ="../../../../static/spotlight/assets/css/bootstrap-responsive.css" rel="stylesheet">
<link href ="../../../../static/spotlight/assets/css/bootstrapValidator.min.css" rel="stylesheet">
<script src="../../../../../static/spotlight/assets/js/jquery-1.11.1.min.js"></script>
<script src="../../../../static/spotlight/assets/js/bootstrap-tab.js"></script>
<script src="../../../../static/spotlight/assets/js/bootstrap.js"></script>
<script src="../../../../static/spotlight/assets/js/bootstrap.min.js"></script>
<script src="../../../../static/spotlight/assets/js/bootstrap-formhelpers.min.js"></script>
<script type="text/javascript" src="../../../../static/spotlight/assets/js/bootstrapValidator.min.js"></script>
the html file for the page with the form
{% extends "spotlight/base.html" %}
{% block content %}
<h3>Update {{ athlete }}</h3>
<br>
<form class="form-horizonal" role="form" action="{% url 'spotlight:athleteedit' athlete.id %}" method="post" id="PersonForm">
{% for field in form %}
<div class="form-group"><p>
{{ field.label_tag }} {{ field }}
{% for error in field.errors %}
<font color="Red">{{ error }}</font>
{% endfor %}
</p></div>
{% endfor %}
{% csrf_token %}
<input class="btn btn-default" type="submit" value="Submit" />
</form>
{% endblock content %}
validators.js
$(document).ready(function emailval() {
$('#PersonForm').bootstrapValidator({
feedbackIcons: {
valid: 'glyphicon glyphicon-ok',
invalid: 'glyphicon glyphicon-remove',
validating: 'glyphicon glyphicon-refresh'
},
fields: {
parent_or_guardian_email: {
validators: {
emailAddress: {
message: 'The value is not a valid email address'
excluded: [':disabled'],
}
}
}
}
});
});
Ok, I figured it out, it was really quite simple. I included this javascript as the last thing before the closing html tag in my base.html file:
<body>
<script type="text/javascript">
$(document).ready(function() {
$('#PersonForm').bootstrapValidator({
message: 'This value is not valid',
feedbackIcons: {
valid: 'glyphicon glyphicon-ok',
invalid: 'glyphicon glyphicon-remove',
validating: 'glyphicon glyphicon-refresh'
},
fields: {
parent_or_guardian_email: {
validators: {
notEmpty: {
message: 'The email address is required and can\'t be empty'
},
emailAddress: {
message: 'The input is not a valid email address'
}
}
},
}
});
});
</script>
</body>
above which (in base.html) I made sure to include the correct files (order does matter)
<link href ="../../../../static/spotlight/assets/css/bootstrap-responsive.css" rel="stylesheet">
<link href ="../../../../static/spotlight/assets/css/bootstrap.css" rel="stylesheet">
<link href ="../../../../static/spotlight/assets/css/bootstrapValidator.min.css" rel="stylesheet">
and made my html for the page containing the form look like this
{% extends "spotlight/base.html" %}
{% block content %}
<h3> Add Athlete</h3>
<br>
<form class="form-horizonal" role="form" action="{% url 'spotlight:addstu' person_id team_id %}" method="post" >
{% for field in form %}
<div class="fieldWrapper"><p>
{% if field.label = "Parent or guardian email" %}
<div class="col-lg-5" id="PersonForm">
{{ field.label_tag }} {{ field }}
</div>
{% else %}
{{ field.label_tag }} {{ field }}
{% for error in field.errors %}
<font color="Red">{{ error }}</font>
{% endfor %}
{% endif %}
</p></div>
{% endfor %}
{% csrf_token %}
<input class="btn btn-default" type="submit" value="add athlete" />
</form>
{% endblock content %}
and I didn't need to create a widget outside the form, just include it like this
parent_or_guardian_email = forms.EmailField(widget =forms.TextInput(attrs={'type':'text', 'class':'form-control','name':"parent_or_guardian_email",'class':"col-lg-5",}),help_text = "Changing this field will create a new user for this email address, if you wish to change this user's contact information"
+" please ask them to do so on their page or contact a site admin ",max_length=75)
for some reason the feedbackIcons aren't showing, but the validation messages are displaying as the user fills in the field, which is what I really needed. Hope someone else finds this so they don't have to bag their head for as long as I did.
I'm so confused. The thing worked fine last night and all of a sudden today stopped working.
The purpose of this page is to generate a list of colors. The templates are located in their own folder called "templates", but GAE can't seem to find these templates at all.
What am I doing wrong here?
main2.py:
import bottle
from bottle import static_file
from google.appengine.ext.webapp import util
from bottle import route
# Load the template system
from jinja2 import Environment, FileSystemLoader
# Indicate from where the templates will be loaded
env = Environment(loader=FileSystemLoader('./templates/'))
# for randomly picking colors
import random
colors = 'green red blue';
#route('/favicon.ico')
def send_image():
filename = 'favicon.ico'
return static_file(filename, root='./images/', mimetype='image/ico');
#route('/hello')
def hello():
template = env.get_template('home.html');
color_list = colors.split();
num_colors = random.randint(0,len(color_list)+1);
color_list = color_list[:num_colors];
return template.render(title="Color List Page!", color_list=color_list);
util.run_wsgi_app(bottle.default_app())
app.yaml
application: yao-webapp2
version: 1
api_version: 1
runtime: python
handlers:
- url: .*
script: main2.py
/templates/base.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>
{% block title %}
I am stupid as heck: I forgot to fill in a title.
{% endblock %}
</title>
</head>
<body>
{% block content %}
No body knows the trouble I've seen.
{% endblock %}
</body>
</html>
/templates/home.html
{% extends "base.html" %}
{% block title %}
{{page_title}}
{% endblock %}
{% block content %}
<h1>Some Colors I know </h1>
<p>I have a list of colors that I know</p>
{% if color_list %}
<ul>
{% for color in color_list %}
<li> {{ color }} </li>
{% endfor %}
</ul>
{% else %}
<p>Oops! No colors.</p>
{% endif %}
{% endblock %}
not sure what error you are getting but i would try to use absolute paths.
instead of
env = Environment(loader=FileSystemLoader('./templates/'))
try to use this code:
templatespath = os.path.join(os.path.dirname(__file__), "templates")
env = Environment(loader=FileSystemLoader(templatespath))