Compare commits

...

3 Commits

Author SHA1 Message Date
ba3af6d664 Working with bootstrap-flask 2023-06-04 22:48:45 +02:00
5d0c774c14 Chapter 12 2023-06-04 11:30:31 +02:00
190cd464cf Chapter 11 2023-06-04 11:24:53 +02:00
14 changed files with 312 additions and 189 deletions

BIN
app.db

Binary file not shown.

View File

@@ -1,11 +1,14 @@
import os import os
from flask import Flask from flask import Flask, request
from config import Config from config import Config
import logging import logging
from logging.handlers import RotatingFileHandler, SMTPHandler from logging.handlers import RotatingFileHandler, SMTPHandler
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate from flask_migrate import Migrate
from flask_login import LoginManager from flask_login import LoginManager
from flask_bootstrap import Bootstrap
from flask_moment import Moment
from flask_babel import Babel, lazy_gettext as _l
app = Flask(__name__) app = Flask(__name__)
app.config.from_object(Config) app.config.from_object(Config)
@@ -18,6 +21,9 @@ migrate = Migrate(app, db)
login = LoginManager(app) login = LoginManager(app)
# sets de default login view # sets de default login view
login.login_view = "login" login.login_view = "login"
bootstrap = Bootstrap(app)
moment = Moment(app)
babel = Babel(app)
if not app.debug: if not app.debug:
if app.config["MAIL_SERVER"]: if app.config["MAIL_SERVER"]:
@@ -53,3 +59,8 @@ if not app.debug:
app.logger.setLevel(logging.INFO) app.logger.setLevel(logging.INFO)
app.logger.info("Microblog startup") app.logger.info("Microblog startup")
from app import routes, models, errors from app import routes, models, errors
@babel.localeselector
def get_locale():
return request.accept_languages.best_match(app.config["LANGUAGES"])

View File

@@ -1,8 +1,6 @@
{% extends 'base.html' %} {% extends "base.html" %}
{% block content %} {% block app_content %}
<h1>File Not Found</h1> <h1>File Not Found</h1>
<p> <p><a href="{{ url_for('index') }}">Back</a></p>
<a href="{{ url_for('index') }}">Back</a>
</p>
{% endblock %} {% endblock %}

View File

@@ -1,9 +1,7 @@
{% extends 'base.html' %} {% extends "base.html" %}
{% block content %} {% block app_content %}
<h1>An unexpected error has occurred</h1> <h1>An unexpected error has occurred</h1>
<p>The administrator has been notified. Sorry for the inconvenience!</p> <p>The administrator has been notified. Sorry for the inconvenience!</p>
<p> <p><a href="{{ url_for('index') }}">Back</a></p>
<a href="{{ url_for('index') }}">Back</a>
</p>
{% endblock %} {% endblock %}

View File

@@ -1,11 +1,17 @@
<table> <table class="table table-hover">
<tr valign="top"> <tr>
<td> <td width="70px">
<img src="{{ post.author.avatar(36) }}" /> <a href="{{ url_for('user', username=post.author.username) }}">
</td> <img src="{{ post.author.avatar(70) }}" />
<td> </a>
<a href="{{ url_for('user', username = post.author.username) }}">{{ post.author.username }}</a> </td>
says:<br />{{ post.body }} <td>
</td> <a href="{{ url_for('user', username=post.author.username) }}">
{{ post.author.username }}
</a>
said {{ moment(post.timestamp).fromNow() }}:
<br>
{{ post.body }}
</td>
</tr> </tr>
</table> </table>

View File

@@ -1,35 +1,55 @@
<html> {% extends 'bootstrap/base.html' %}
<head>
{% if title %}
<title>{{ title }} - microblog</title>
{% else %}
<title>microblog</title>
{% endif %}
</head>
<body>
<div>
Microblog:
<a href="{{ url_for('index') }}">Home</a>
{% if current_user.is_anonymous %}
<a href="{{ url_for('login') }}">Login</a>
{% else %}
<a href="{{ url_for('user', username = current_user.username) }}">Profile</a>
<a href="{{ url_for('explore') }}">Explore</a>
<a href="{{ url_for('logout') }}">Logout</a>
{% endif %}
</div>
<hr />
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% block content %}
{% endblock %} {% block title %}
</body> {% if title %}{{ title }} - Microblog{% else %}Welcome to Microblog{% endif %}
</html> {% endblock %}
{% block navbar %}
<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{{ url_for('index') }}">Microblog</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="{{ url_for('index') }}">Home</a></li>
<li><a href="{{ url_for('explore') }}">Explore</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
{% if current_user.is_anonymous %}
<li><a href="{{ url_for('login') }}">Login</a></li>
{% else %}
<li><a href="{{ url_for('user', username=current_user.username) }}">Profile</a></li>
<li><a href="{{ url_for('logout') }}">Logout</a></li>
{% endif %}
</ul>
</div>
</div>
</nav>
{% endblock %}
{% block content %}
<div class="container">
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-info" role="alert">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
{# application content needs to be provided in the app_content block #}
{% block app_content %}{% endblock %}
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
{{ moment.include_moment() }}
{% endblock %}

View File

@@ -1,23 +1,11 @@
{% extends 'base.html' %} {% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block content %} {% block app_content %}
<h1>Edit Profile</h1> <h1>Edit Profile</h1>
<form action="" method="post"> <div class="row">
{{ form.hidden_tag() }} <div class="col-md-4">
<p> {{ wtf.quick_form(form) }}
{{ form.username.label }}<br /> </div>
{{ form.username(size = 32) }}<br /> </div>
{% for error in form.username.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.about_me.label }}<br />
{{ form.about_me(cols = 50, rows = 4) }}<br />
{% for error in form.about_me.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>{{ form.submit() }}</p>
</form>
{% endblock %} {% endblock %}

View File

@@ -1,27 +1,27 @@
{% extends 'base.html' %} {% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block content %} {% block app_content %}
<h1>Hi, {{ current_user.username }}!</h1> <h1>Hi, {{ current_user.username }}!</h1>
{% if form %} {% if form %}
<form action="" method="post"> {{ wtf.quick_form(form) }}
{{ form.hidden_tag() }} <br>
<p> {% endif %}
{{ form.post.label }}<br /> {% for post in posts %}
{{ form.post(cols = 32, rows = 4) }}<br /> {% include '_post.html' %}
{% for error in form.post.errors %} {% endfor %}
<span style="color: red;">[{{ error }}]</span> <nav aria-label="...">
{% endfor %} <ul class="pager">
</p> <li class="previous{% if not prev_url %} disabled{% endif %}">
<p>{{ form.submit() }}</p> <a href="{{ prev_url or '#' }}">
</form> <span aria-hidden="true">&larr;</span> Newer posts
{% endif %} </a>
{% for post in posts %} </li>
{% include '_post.html' %} <li class="next{% if not next_url %} disabled{% endif %}">
{% endfor %} <a href="{{ next_url or '#' }}">
{% if prev_url %} Older posts <span aria-hidden="true">&rarr;</span>
<a href="{{ prev_url }}">Newer posts</a> </a>
{% endif %} </li>
{% if next_url %} </ul>
<a href="{{ next_url }}">Older posts</a> </nav>
{% endif %} {% endblock %}
{% endblock %}s

View File

@@ -1,37 +1,11 @@
{% extends 'base.html' %} {% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block content %} {% block app_content %}
<h1>Register</h1> <h1>Register</h1>
<form action="" method="post"> <div class="row">
{{ form.hidden_tag() }} <div class="col-md-4">
<p> {{ wtf.quick_form(form) }}
{{ form.username.label }}<br /> </div>
{{ form.username(size = 32) }}<br /> </div>
{% for error in form.username.errors %} {% endblock %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.email.label }}<br />
{{ form.email(size = 64) }}<br />
{% for error in form.email.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.password.label }}<br />
{{ form.password(size = 32) }}<br />
{% for error in form.password.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.password2.label }}<br />
{{ form.password2(size = 32) }}<br />
{% for error in form.password2.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>{{ form.submit() }}</p>
</form>
{% endblock %}

View File

@@ -0,0 +1,11 @@
{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<h1>Reset Your Password</h1>
<div class="row">
<div class="col-md-4">
{{ wtf.quick_form(form) }}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,11 @@
{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<h1>Reset Password</h1>
<div class="row">
<div class="col-md-4">
{{ wtf.quick_form(form) }}
</div>
</div>
{% endblock %}

View File

@@ -1,50 +1,51 @@
{% extends 'base.html' %} {% extends "base.html" %}
{% block content %} {% block app_content %}
<table> <table class="table table-hover">
<tr valign="top"> <tr>
<td> <td width="256px"><img src="{{ user.avatar(256) }}"></td>
<img src="{{ user.avatar(128) }}" /> <td>
</td> <h1>User: {{ user.username }}</h1>
<td> {% if user.about_me %}<p>{{ user.about_me }}</p>{% endif %}
<h1>User: {{ user.username }}</h1> {% if user.last_seen %}
{% if user.about_me %} <p>Last seen on: {{ moment(user.last_seen).format('LLL') }}</p>
<p>{{ user.about_me }}</p> {% endif %}
{% endif %} <p>{{ user.followers.count() }} followers, {{ user.followed.count() }} following.</p>
{% if user.last_seen %} {% if user == current_user %}
<p>Last seen on: {{ user.last_seen }}</p> <p><a href="{{ url_for('edit_profile') }}">Edit your profile</a></p>
{% endif %} {% elif not current_user.is_following(user) %}
</td> <p>
</tr> <form action="{{ url_for('follow', username=user.username) }}" method="post">
</table> {{ form.hidden_tag() }}
<p>{{ user.followers.count() }} followers, {{ user.followed.count() }} following.</p> {{ form.submit(value='Follow', class_='btn btn-default') }}
{% if user == current_user %} </form>
<p> </p>
<a href="{{ url_for('edit_profile') }}">Edit your profile</a> {% else %}
</p> <p>
{% elif not current_user.is_following(user) %} <form action="{{ url_for('unfollow', username=user.username) }}" method="post">
<p> {{ form.hidden_tag() }}
<form action="{{ url_for('follow', username = user.username) }}" method="post"> {{ form.submit(value='Unfollow', class_='btn btn-default') }}
{{ form.hidden_tag() }} </form>
{{ form.submit(value = 'Follow') }} </p>
</form> {% endif %}
</p> </td>
{% else %} </tr>
<p> </table>
<form action="{{ url_for('unfollow', username = user.username) }}" method="post"> {% for post in posts %}
{{ form.hidden_tag() }} {% include '_post.html' %}
{{ form.submit(value = 'Unfollow') }} {% endfor %}
</form> <nav aria-label="...">
</p> <ul class="pager">
{% endif %} <li class="previous{% if not prev_url %} disabled{% endif %}">
<hr /> <a href="{{ prev_url or '#' }}">
{% for post in posts %} <span aria-hidden="true">&larr;</span> Newer posts
{% include '_post.html' %} </a>
{% endfor %} </li>
{% if prev_url %} <li class="next{% if not next_url %} disabled{% endif %}">
<a href="{{ prev_url }}">Newer posts</a> <a href="{{ next_url or '#' }}">
{% endif %} Older posts <span aria-hidden="true">&rarr;</span>
{% if next_url %} </a>
<a href="{{ next_url }}">Older posts</a> </li>
{% endif %} </ul>
{% endblock %} </nav>
{% endblock %}

View File

@@ -16,3 +16,4 @@ class Config(object):
MAIL_PASSWORD = os.environ.get("MAIL_PASSWORD") MAIL_PASSWORD = os.environ.get("MAIL_PASSWORD")
ADMINS = ["your-email@example.com"] ADMINS = ["your-email@example.com"]
POSTS_PER_PAGE = 25 POSTS_PER_PAGE = 25
LANGUAGES = ["en", "es"]

104
logs/microblog.log.1 Normal file
View File

@@ -0,0 +1,104 @@
2023-06-03 21:28:36,207 INFO: Microblog startup [in /Users/rmontanana/Code/microblog/app/__init__.py:54]
2023-06-03 21:28:38,642 INFO: Microblog startup [in /Users/rmontanana/Code/microblog/app/__init__.py:54]
2023-06-03 21:28:40,330 INFO: Microblog startup [in /Users/rmontanana/Code/microblog/app/__init__.py:54]
2023-06-03 21:28:41,629 INFO: Microblog startup [in /Users/rmontanana/Code/microblog/app/__init__.py:54]
2023-06-03 21:29:35,163 INFO: Microblog startup [in /Users/rmontanana/Code/microblog/app/__init__.py:54]
2023-06-03 21:30:43,525 INFO: Microblog startup [in /Users/rmontanana/Code/microblog/app/__init__.py:54]
2023-06-03 21:32:38,700 INFO: Microblog startup [in /Users/rmontanana/Code/microblog/app/__init__.py:54]
2023-06-03 21:33:27,453 INFO: Microblog startup [in /Users/rmontanana/Code/microblog/app/__init__.py:54]
2023-06-03 21:34:29,733 INFO: Microblog startup [in /Users/rmontanana/Code/microblog/app/__init__.py:54]
2023-06-03 21:34:49,116 INFO: Microblog startup [in /Users/rmontanana/Code/microblog/app/__init__.py:54]
2023-06-03 21:35:13,268 INFO: Microblog startup [in /Users/rmontanana/Code/microblog/app/__init__.py:54]
2023-06-03 21:36:44,548 INFO: Microblog startup [in /Users/rmontanana/Code/microblog/app/__init__.py:54]
2023-06-03 21:37:05,324 INFO: Microblog startup [in /Users/rmontanana/Code/microblog/app/__init__.py:54]
2023-06-03 21:37:15,359 INFO: Microblog startup [in /Users/rmontanana/Code/microblog/app/__init__.py:54]
2023-06-04 01:01:14,308 INFO: Microblog startup [in /Users/rmontanana/Code/microblog/app/__init__.py:54]
2023-06-04 01:01:31,728 INFO: Microblog startup [in /Users/rmontanana/Code/microblog/app/__init__.py:54]
2023-06-04 01:02:26,464 INFO: Microblog startup [in /Users/rmontanana/Code/microblog/app/__init__.py:54]
2023-06-04 01:02:39,866 INFO: Microblog startup [in /Users/rmontanana/Code/microblog/app/__init__.py:54]
2023-06-04 01:03:05,683 INFO: Microblog startup [in /Users/rmontanana/Code/microblog/app/__init__.py:54]
2023-06-04 01:03:35,114 INFO: Microblog startup [in /Users/rmontanana/Code/microblog/app/__init__.py:54]
2023-06-04 01:15:47,183 INFO: Microblog startup [in /Users/rmontanana/Code/microblog/app/__init__.py:54]
2023-06-04 01:15:53,685 INFO: Microblog startup [in /Users/rmontanana/Code/microblog/app/__init__.py:54]
2023-06-04 01:19:54,421 INFO: Microblog startup [in /Users/rmontanana/Code/microblog/app/__init__.py:54]
2023-06-04 11:09:37,753 INFO: Microblog startup [in /Users/rmontanana/Code/microblog/app/__init__.py:54]
2023-06-04 11:16:03,037 ERROR: Exception on /explore [GET] [in /Users/rmontanana/miniconda3/envs/microblog/lib/python3.11/site-packages/flask/app.py:1741]
Traceback (most recent call last):
File "/Users/rmontanana/miniconda3/envs/microblog/lib/python3.11/site-packages/flask/app.py", line 2525, in wsgi_app
response = self.full_dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/rmontanana/miniconda3/envs/microblog/lib/python3.11/site-packages/flask/app.py", line 1822, in full_dispatch_request
rv = self.handle_user_exception(e)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/rmontanana/miniconda3/envs/microblog/lib/python3.11/site-packages/flask/app.py", line 1820, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/rmontanana/miniconda3/envs/microblog/lib/python3.11/site-packages/flask/app.py", line 1796, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/rmontanana/miniconda3/envs/microblog/lib/python3.11/site-packages/flask_login/utils.py", line 290, in decorated_view
return current_app.ensure_sync(func)(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/rmontanana/Code/microblog/app/routes.py", line 193, in explore
return render_template(
^^^^^^^^^^^^^^^^
File "/Users/rmontanana/miniconda3/envs/microblog/lib/python3.11/site-packages/flask/templating.py", line 147, in render_template
return _render(app, template, context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/rmontanana/miniconda3/envs/microblog/lib/python3.11/site-packages/flask/templating.py", line 130, in _render
rv = template.render(context)
^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/rmontanana/miniconda3/envs/microblog/lib/python3.11/site-packages/jinja2/environment.py", line 1301, in render
self.environment.handle_exception()
File "/Users/rmontanana/miniconda3/envs/microblog/lib/python3.11/site-packages/jinja2/environment.py", line 936, in handle_exception
raise rewrite_traceback_stack(source=source)
File "/Users/rmontanana/Code/microblog/app/templates/index.html", line 1, in top-level template code
{% extends 'base.html' %}
File "/Users/rmontanana/Code/microblog/app/templates/base.html", line 1, in top-level template code
{% extends 'bootstrap/base.html' %}
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/rmontanana/miniconda3/envs/microblog/lib/python3.11/site-packages/flask/templating.py", line 62, in get_source
return self._get_source_fast(environment, template)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/rmontanana/miniconda3/envs/microblog/lib/python3.11/site-packages/flask/templating.py", line 98, in _get_source_fast
raise TemplateNotFound(template)
jinja2.exceptions.TemplateNotFound: bootstrap/base.html
2023-06-04 11:22:13,327 ERROR: Exception on /explore [GET] [in /Users/rmontanana/miniconda3/envs/microblog/lib/python3.11/site-packages/flask/app.py:1741]
Traceback (most recent call last):
File "/Users/rmontanana/miniconda3/envs/microblog/lib/python3.11/site-packages/flask/app.py", line 2525, in wsgi_app
response = self.full_dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/rmontanana/miniconda3/envs/microblog/lib/python3.11/site-packages/flask/app.py", line 1822, in full_dispatch_request
rv = self.handle_user_exception(e)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/rmontanana/miniconda3/envs/microblog/lib/python3.11/site-packages/flask/app.py", line 1820, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/rmontanana/miniconda3/envs/microblog/lib/python3.11/site-packages/flask/app.py", line 1796, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/rmontanana/miniconda3/envs/microblog/lib/python3.11/site-packages/flask_login/utils.py", line 290, in decorated_view
return current_app.ensure_sync(func)(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/rmontanana/Code/microblog/app/routes.py", line 193, in explore
return render_template(
^^^^^^^^^^^^^^^^
File "/Users/rmontanana/miniconda3/envs/microblog/lib/python3.11/site-packages/flask/templating.py", line 147, in render_template
return _render(app, template, context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/rmontanana/miniconda3/envs/microblog/lib/python3.11/site-packages/flask/templating.py", line 130, in _render
rv = template.render(context)
^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/rmontanana/miniconda3/envs/microblog/lib/python3.11/site-packages/jinja2/environment.py", line 1301, in render
self.environment.handle_exception()
File "/Users/rmontanana/miniconda3/envs/microblog/lib/python3.11/site-packages/jinja2/environment.py", line 936, in handle_exception
raise rewrite_traceback_stack(source=source)
File "/Users/rmontanana/Code/microblog/app/templates/index.html", line 2, in top-level template code
{% import 'bootstrap/wtf.html' as wtf %}
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/rmontanana/miniconda3/envs/microblog/lib/python3.11/site-packages/flask/templating.py", line 62, in get_source
return self._get_source_fast(environment, template)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/rmontanana/miniconda3/envs/microblog/lib/python3.11/site-packages/flask/templating.py", line 98, in _get_source_fast
raise TemplateNotFound(template)
jinja2.exceptions.TemplateNotFound: bootstrap/wtf.html