提交 46f3713c 编写于 作者: C Corey Schafer

Flask and Django deployment changes

上级 07ab8384
from django.contrib import admin
from .models import Post
admin.site.register(Post)
from django.apps import AppConfig
class BlogConfig(AppConfig):
name = 'blog'
# Generated by Django 2.1 on 2018-08-28 02:32
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Post',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=100)),
('content', models.TextField()),
('date_posted', models.DateTimeField(default=django.utils.timezone.now)),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
from django.urls import reverse
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.pk})
body {
background: #fafafa;
color: #333333;
margin-top: 5rem;
}
h1, h2, h3, h4, h5, h6 {
color: #444444;
}
ul {
margin: 0;
}
.bg-steel {
background-color: #5f788a;
}
.site-header .navbar-nav .nav-link {
color: #cbd5db;
}
.site-header .navbar-nav .nav-link:hover {
color: #ffffff;
}
.site-header .navbar-nav .nav-link.active {
font-weight: 500;
}
.content-section {
background: #ffffff;
padding: 10px 20px;
border: 1px solid #dddddd;
border-radius: 3px;
margin-bottom: 20px;
}
.article-title {
color: #444444;
}
a.article-title:hover {
color: #428bca;
text-decoration: none;
}
.article-content {
white-space: pre-line;
}
.article-img {
height: 65px;
width: 65px;
margin-right: 16px;
}
.article-metadata {
padding-bottom: 1px;
margin-bottom: 4px;
border-bottom: 1px solid #e3e3e3
}
.article-metadata a:hover {
color: #333;
text-decoration: none;
}
.article-svg {
width: 25px;
height: 25px;
vertical-align: middle;
}
.account-img {
height: 125px;
width: 125px;
margin-right: 20px;
margin-bottom: 16px;
}
.account-heading {
font-size: 2.5rem;
}
{% extends "blog/base.html" %}
{% block content %}
<h1>About Page</h1>
{% endblock content %}
{% load static %}
<!DOCTYPE html>
<html>
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="{% static 'blog/main.css' %}">
{% if title %}
<title>Django Blog - {{ title }}</title>
{% else %}
<title>Django Blog</title>
{% endif %}
</head>
<body>
<header class="site-header">
<nav class="navbar navbar-expand-md navbar-dark bg-steel fixed-top">
<div class="container">
<a class="navbar-brand mr-4" href="{% url 'blog-home' %}">Django Blog</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarToggle" aria-controls="navbarToggle" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarToggle">
<div class="navbar-nav mr-auto">
<a class="nav-item nav-link" href="{% url 'blog-home' %}">Home</a>
<a class="nav-item nav-link" href="{% url 'blog-about' %}">About</a>
</div>
<!-- Navbar Right Side -->
<div class="navbar-nav">
{% if user.is_authenticated %}
<a class="nav-item nav-link" href="{% url 'post-create' %}">New Post</a>
<a class="nav-item nav-link" href="{% url 'profile' %}">Profile</a>
<a class="nav-item nav-link" href="{% url 'logout' %}">Logout</a>
{% else %}
<a class="nav-item nav-link" href="{% url 'login' %}">Login</a>
<a class="nav-item nav-link" href="{% url 'register' %}">Register</a>
{% endif %}
</div>
</div>
</div>
</nav>
</header>
<main role="main" class="container">
<div class="row">
<div class="col-md-8">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% block content %}{% endblock %}
</div>
<div class="col-md-4">
<div class="content-section">
<h3>Our Sidebar</h3>
<p class='text-muted'>You can put any information here you'd like.
<ul class="list-group">
<li class="list-group-item list-group-item-light">Latest Posts</li>
<li class="list-group-item list-group-item-light">Announcements</li>
<li class="list-group-item list-group-item-light">Calendars</li>
<li class="list-group-item list-group-item-light">etc</li>
</ul>
</p>
</div>
</div>
</div>
</main>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
</body>
</html>
{% extends "blog/base.html" %}
{% block content %}
{% for post in posts %}
<article class="media content-section">
<img class="rounded-circle article-img" src="{{ post.author.profile.image.url }}">
<div class="media-body">
<div class="article-metadata">
<a class="mr-2" href="{% url 'user-posts' post.author.username %}">{{ post.author }}</a>
<small class="text-muted">{{ post.date_posted|date:"F d, Y" }}</small>
</div>
<h2><a class="article-title" href="{% url 'post-detail' post.id %}">{{ post.title }}</a></h2>
<p class="article-content">{{ post.content }}</p>
</div>
</article>
{% endfor %}
{% if is_paginated %}
{% if page_obj.has_previous %}
<a class="btn btn-outline-info mb-4" href="?page=1">First</a>
<a class="btn btn-outline-info mb-4" href="?page={{ page_obj.previous_page_number }}">Previous</a>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<a class="btn btn-info mb-4" href="?page={{ num }}">{{ num }}</a>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<a class="btn btn-outline-info mb-4" href="?page={{ num }}">{{ num }}</a>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<a class="btn btn-outline-info mb-4" href="?page={{ page_obj.next_page_number }}">Next</a>
<a class="btn btn-outline-info mb-4" href="?page={{ page_obj.paginator.num_pages }}">Last</a>
{% endif %}
{% endif %}
{% endblock content %}
{% extends "blog/base.html" %}
{% block content %}
<div class="content-section">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Delete Post</legend>
<h2>Are you sure you want to delete the post "{{ object.title }}"</h2>
</fieldset>
<div class="form-group">
<button class="btn btn-outline-danger" type="submit">Yes, Delete</button>
<a class="btn btn-outline-secondary" href="{% url 'post-detail' object.id %}">Cancel</a>
</div>
</form>
</div>
{% endblock content %}
{% extends "blog/base.html" %}
{% block content %}
<article class="media content-section">
<img class="rounded-circle article-img" src="{{ object.author.profile.image.url }}">
<div class="media-body">
<div class="article-metadata">
<a class="mr-2" href="{% url 'user-posts' object.author.username %}">{{ object.author }}</a>
<small class="text-muted">{{ object.date_posted|date:"F d, Y" }}</small>
{% if object.author == user %}
<div>
<a class="btn btn-secondary btn-sm mt-1 mb-1" href="{% url 'post-update' object.id %}">Update</a>
<a class="btn btn-danger btn-sm mt-1 mb-1" href="{% url 'post-delete' object.id %}">Delete</a>
</div>
{% endif %}
</div>
<h2 class="article-title">{{ object.title }}</h2>
<p class="article-content">{{ object.content }}</p>
</div>
</article>
{% endblock content %}
{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="content-section">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Blog Post</legend>
{{ form|crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Post</button>
</div>
</form>
</div>
{% endblock content %}
{% extends "blog/base.html" %}
{% block content %}
<h1 class="mb-3">Posts by {{ view.kwargs.username }} ({{ page_obj.paginator.count }})</h1>
{% for post in posts %}
<article class="media content-section">
<img class="rounded-circle article-img" src="{{ post.author.profile.image.url }}">
<div class="media-body">
<div class="article-metadata">
<a class="mr-2" href="{% url 'user-posts' post.author.username %}">{{ post.author }}</a>
<small class="text-muted">{{ post.date_posted|date:"F d, Y" }}</small>
</div>
<h2><a class="article-title" href="{% url 'post-detail' post.id %}">{{ post.title }}</a></h2>
<p class="article-content">{{ post.content }}</p>
</div>
</article>
{% endfor %}
{% if is_paginated %}
{% if page_obj.has_previous %}
<a class="btn btn-outline-info mb-4" href="?page=1">First</a>
<a class="btn btn-outline-info mb-4" href="?page={{ page_obj.previous_page_number }}">Previous</a>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<a class="btn btn-info mb-4" href="?page={{ num }}">{{ num }}</a>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<a class="btn btn-outline-info mb-4" href="?page={{ num }}">{{ num }}</a>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<a class="btn btn-outline-info mb-4" href="?page={{ page_obj.next_page_number }}">Next</a>
<a class="btn btn-outline-info mb-4" href="?page={{ page_obj.paginator.num_pages }}">Last</a>
{% endif %}
{% endif %}
{% endblock content %}
from django.test import TestCase
# Create your tests here.
from django.urls import path
from .views import (
PostListView,
PostDetailView,
PostCreateView,
PostUpdateView,
PostDeleteView,
UserPostListView
)
from . import views
urlpatterns = [
path('', PostListView.as_view(), name='blog-home'),
path('user/<str:username>', UserPostListView.as_view(), name='user-posts'),
path('post/<int:pk>/', PostDetailView.as_view(), name='post-detail'),
path('post/new/', PostCreateView.as_view(), name='post-create'),
path('post/<int:pk>/update/', PostUpdateView.as_view(), name='post-update'),
path('post/<int:pk>/delete/', PostDeleteView.as_view(), name='post-delete'),
path('about/', views.about, name='blog-about'),
]
from django.shortcuts import render, get_object_or_404
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.contrib.auth.models import User
from django.views.generic import (
ListView,
DetailView,
CreateView,
UpdateView,
DeleteView
)
from .models import Post
def home(request):
context = {
'posts': Post.objects.all()
}
return render(request, 'blog/home.html', context)
class PostListView(ListView):
model = Post
template_name = 'blog/home.html' # <app>/<model>_<viewtype>.html
context_object_name = 'posts'
ordering = ['-date_posted']
paginate_by = 5
class UserPostListView(ListView):
model = Post
template_name = 'blog/user_posts.html' # <app>/<model>_<viewtype>.html
context_object_name = 'posts'
paginate_by = 5
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Post.objects.filter(author=user).order_by('-date_posted')
class PostDetailView(DetailView):
model = Post
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'content']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Post
fields = ['title', 'content']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def test_func(self):
post = self.get_object()
if self.request.user == post.author:
return True
return False
class PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Post
success_url = '/'
def test_func(self):
post = self.get_object()
if self.request.user == post.author:
return True
return False
def about(request):
return render(request, 'blog/about.html', {'title': 'About'})
"""
Django settings for django_project project.
Generated by 'django-admin startproject' using Django 2.1.
For more information on this file, see
https://docs.djangoproject.com/en/2.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.1/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'exhlfdat&vfum(-34*c2uroi(($ww(yo$9pv98=e6p^gl(-eoj'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'blog.apps.BlogConfig',
'users.apps.UsersConfig',
'crispy_forms',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'django_project.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'django_project.wsgi.application'
# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/2.1/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.1/howto/static-files/
STATIC_URL = '/static/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
CRISPY_TEMPLATE_PACK = 'bootstrap4'
LOGIN_REDIRECT_URL = 'blog-home'
LOGIN_URL = 'login'
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = os.environ.get('EMAIL_USER')
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_PASS')
"""django_project URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.1/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.contrib.auth import views as auth_views
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from users import views as user_views
urlpatterns = [
path('admin/', admin.site.urls),
path('register/', user_views.register, name='register'),
path('profile/', user_views.profile, name='profile'),
path('login/', auth_views.LoginView.as_view(template_name='users/login.html'), name='login'),
path('logout/', auth_views.LogoutView.as_view(template_name='users/logout.html'), name='logout'),
path('password-reset/',
auth_views.PasswordResetView.as_view(
template_name='users/password_reset.html'
),
name='password_reset'),
path('password-reset/done/',
auth_views.PasswordResetDoneView.as_view(
template_name='users/password_reset_done.html'
),
name='password_reset_done'),
path('password-reset-confirm/<uidb64>/<token>/',
auth_views.PasswordResetConfirmView.as_view(
template_name='users/password_reset_confirm.html'
),
name='password_reset_confirm'),
path('password-reset-complete/',
auth_views.PasswordResetCompleteView.as_view(
template_name='users/password_reset_complete.html'
),
name='password_reset_complete'),
path('', include('blog.urls')),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
"""
WSGI config for django_project project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_project.settings')
application = get_wsgi_application()
#!/usr/bin/env python
import os
import sys
if __name__ == '__main__':
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_project.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
[
{
"title": "My Updated Post",
"content": "My first updated post!\r\n\r\nThis is exciting!",
"user_id": 1
},
{
"title": "A Second Post",
"content": "This is a post from a different user...",
"user_id": 2
},
{
"title": "Top 5 Programming Lanaguages",
"content": "Te melius apeirian postulant cum, labitur admodum cu eos! Tollit equidem constituto ut has. Et per ponderum sadipscing, eu vero dolores recusabo nec! Eum quas epicuri at, eam albucius phaedrum ad, no eum probo fierent singulis. Dicat corrumpit definiebas id usu, in facete scripserit eam.\r\n\r\nVim ei exerci nusquam. Agam detraxit an quo? Quo et partem bonorum sensibus, mutat minimum est ad. In paulo essent signiferumque his, quaestio sadipscing theophrastus ad has. Ancillae appareat qualisque ei has, usu ne assum zril disputationi, sed at gloriatur persequeris.",
"user_id": 1
},
{
"title": "Sublime Text Tips and Tricks",
"content": "Ea vix dico modus voluptatibus, mel iudico suavitate iracundia eu. Tincidunt voluptatibus pro eu? Nulla omittam eligendi his ne, suas putant ut pri. Ullum repudiare at duo, ut cum habeo minim laudem, dicit libris antiopam has ut! Ex movet feugait mea, eu vim impetus nostrud cotidieque.\r\n\r\nEi suas similique quo, his simul viris congue ex? Graeci possit in est, ne qui minim delectus invenire. Mei ad error homero maluisset, tacimates assentior per in, vix ut vocent accusata! Mei eu inermis pericula patrioque? Debet denique sea at, ad cibo reformidans theophrastus per, cu inermis maiestatis vim!\r\n\r\nUt odio feugiat voluptua est, euismod volutpat qualisque at sit, has ex dicit ornatus inimicus! Eu ferri laoreet vel, dicat corrumpit dissentias nec in. Illum dissentiunt eam ei, praesent voluptatum pri in? Ius in inani petentium, hinc elitr vivendum an vis, in vero dolores electram ius?",
"user_id": 1
},
{
"title": "Best Python IDEs",
"content": "Elit contentiones nam no, sea ut consul adipiscing. Etiam velit ei usu, sonet clita nonumy eu eum. Usu ea utroque facilisi, cu mel fugit tantas legimus, te vix quem nominavi. Prima deserunt evertitur ne qui, nam reprimique appellantur ne.",
"user_id": 1
},
{
"title": "Flask vs Django - Which Is Better?",
"content": "Ei dicta apeirian deterruisset eam, cu offendit invenire pri, cu possim vivendo vix? Nam nihil evertitur ad, ne vim nonumy legendos iracundia. Vix nulla dolorem intellegebat ea? Te per vide paulo dolor, eum ea erant placerat constituam? Dolores accumsan eum at.\r\n\r\nInteresset consequuntur id vix. Eam id decore latine, iusto imperdiet ei qui. In ludus consul reformidans eam. Nec in recusabo posidonium, cu tantas volumus mnesarchum pro. Nam ut docendi evertitur, possim menandri persecuti ne sed, cum saepe ornatus delenit ei?\r\n\r\nIn mel debet aliquam. In his etiam legere, doming nominavi consetetur has ad, decore reprimique ea usu. Eam magna graeci suavitate cu, facete delenit cum ne. Ponderum evertitur tincidunt ei mel, ius ei stet euismod docendi.",
"user_id": 2
},
{
"title": "You Won't Believe These Clickbait Titles!",
"content": "Cu justo honestatis mel, pro ei appareat mediocrem suavitate. No his omnis ridens. Ludus ornatus voluptatum mei ut, an mentitum noluisse forensibus cum. Eam affert pertinax consequuntur ei, nisl zril meliore te vis? Ad animal persius concludaturque vix, eu graece audiam mel.\r\n\r\nVitae libris mentitum pri in. Cu rebum veritus sea, ex usu consul dolorum, pro tale maluisset consulatu ut. Quo ad clita persius ancillae. Vel illud blandit at, vel eu hinc graeco, usu doctus praesent ea! Vim rebum deserunt ex.\r\n\r\nIus lorem omittam id, est suavitate definitionem ad! Id vim insolens tacimates, pri at decore causae. Ex duo bonorum repudiandae? Vix no vidit facete impedit. An oportere indoctum eam.",
"user_id": 2
},
{
"title": "These Beers Will Improve Your Programming",
"content": "Sanctus senserit vis id, ut eum iuvaret invidunt constituam? Nonumes facilis mei an, ad elit explicari persequeris pri, dico recusabo quo id? At mea lorem repudiandae. Sed causae sensibus forensibus ea, ne ornatus suscipiantur consectetuer mel, affert nostro nominati cu qui. Te sanctus constituto est, corrumpit pertinacia eos et, mei libris persequeris an.\r\n\r\nQuo fuisset sensibus in. Ad est assueverit adversarium, viris aperiri numquam est ad. Pro mediocrem iudicabit ei! Cu aperiam diceret sit.",
"user_id": 1
},
{
"title": "List of PyCon 2018 Talks",
"content": "Has ea verear adolescens, elit justo constituam duo in, vix an copiosae contentiones. Eos persius consequuntur no, esse percipit cum ea, per modus harum praesent at. Et clita delenit luptatum usu? No cum interpretaris concludaturque. Congue pertinax ea mea.\r\n\r\nBrute iracundia philosophia ei quo, nam at adhuc idque, ex dolor homero mei. No mea affert tacimates pertinacia, in maluisset dissentias consectetuer mei, vel no aliquam splendide. In has nobis vocent adipisci? Pri clita delicata in, iusto viris scripserit vim in? Sit in lorem complectitur. Sanctus eloquentiam eum ut, et sumo apeirian mea? Vim te affert populo voluptaria, utinam consul ad duo.",
"user_id": 1
},
{
"title": "How Dogs in the Workplace Boosts Productivity",
"content": "Has ea verear adolescens, elit justo constituam duo in, vix an copiosae contentiones. Eos persius consequuntur no, esse percipit cum ea, per modus harum praesent at. Et clita delenit luptatum usu? No cum interpretaris concludaturque. Congue pertinax ea mea.\r\n\r\nBrute iracundia philosophia ei quo, nam at adhuc idque, ex dolor homero mei. No mea affert tacimates pertinacia, in maluisset dissentias consectetuer mei, vel no aliquam splendide. In has nobis vocent adipisci? Pri clita delicata in, iusto viris scripserit vim in? Sit in lorem complectitur. Sanctus eloquentiam eum ut, et sumo apeirian mea? Vim te affert populo voluptaria, utinam consul ad duo.",
"user_id": 1
},
{
"title": "The Best Programming Podcasts",
"content": "Vidisse malorum platonem vel no. Persecuti adversarium ut sit, quo et stet velit mundi! Id per homero expetenda. Est brute adipisci et!\r\n\r\nLorem aliquip has in, quo debet ceteros sadipscing ne! An sea odio ornatus inermis, an per ipsum persecuti dissentiunt, no mea bonorum pertinacia delicatissimi? Ne sumo diceret mea, percipit repudiare eam no! Pro et lorem accommodare. At eius novum phaedrum mei?\r\n\r\nIgnota conclusionemque mei no, eam ut munere fierent pertinacia. Ea enim insolens gloriatur duo, quis vituperatoribus pro no! Ei sed bonorum reprehendunt, aliquam nominavi his et. Magna decore referrentur id nec. Cum rebum ludus inimicus no, id cum iusto labores maluisset!\r\n\r\nQui no omnis numquam apeirian, et vide interesset cum? Et nec nulla signiferumque. Enim instructior eos ei, solum tollit phaedrum his in? No vix malorum ornatus, cu quo hinc everti iracundia, essent eruditi efficiendi ut nam. Altera saperet usu eu, errem expetenda cu duo. Has dolor splendide et, no mel cibo ancillae voluptatum, mutat antiopam deterruisset ei qui. Dolores scripserit concludaturque est id, ea animal facilisi splendide qui, quo at animal voluptua instructior.\r\n\r\nMeis voluptatum eu eum.",
"user_id": 1
},
{
"title": "Tips for Public Speaking",
"content": "Ex eam doctus accommodare. Ut oratio vivendo intellegebat qui. Ius ne doming petentium. Pri congue delectus ad, accumsan molestiae disputando te mea. Nam case inani eligendi at, per te esse iudico. Feugiat patrioque mei ad, harum mundi adversarium an per!\r\n\r\nAncillae verterem eleifend his at? Nam vidit iusto petentium at, vis nusquam dissentias cu, etiam doctus adversarium eam no. At alterum definiebas efficiantur eos, pro labitur vituperatoribus ne, eu odio legere vim. Ad nec verear appellantur? Ad qui vulputate persequeris.",
"user_id": 2
},
{
"title": "Best Programmers Throughout History",
"content": "Mel nulla legimus senserit id. Vim purto tractatos in, te vix error regione, erant laudem legere an vel. Falli fierent ius ex! In legere iriure est, id vis prima maluisset, purto numquam inimicus ut eos! In duo antiopam salutatus, an vel quodsi virtute definitiones.\r\n\r\nEst te sumo voluptaria, ius no putant argumentum, alienum ocurreret vim cu? Volumus democritum no vel, virtute commune an est. Vel te propriae lobortis rationibus, no eum odio neglegentur? Duo an sumo ignota latine! Nec mazim aperiam percipitur eu, id his dicit omnium.",
"user_id": 2
},
{
"title": "How To Create A YouTube Channel",
"content": "Sit et novum omnes. Nec ea quas minim tractatos, usu in aperiam mentitum necessitatibus, ut omnis equidem moderatius quo. Eos ad putent aeterno praesent. Eos omnium similique id, his accommodare philosophia at. Causae lucilius similique in mea, ut regione tritani voluptatibus mel! At possim offendit eum, aeque denique prodesset pro te?\r\n\r\nAt pro quem laudem. Et agam democritum eos? Ea quod probatus usu, no ferri fabulas cotidieque mei? Numquam nusquam quo in, quo et molestiae complectitur. Nihil semper ei qui.\r\n\r\nModo omnes forensibus duo ex, te est diceret bonorum labores! Magna ponderum eos ea. Cu vim diceret mnesarchum, graeci periculis in vis. Est no iriure suavitate!",
"user_id": 2
},
{
"title": "How I Record My Videos",
"content": "Ad vel possim delicatissimi, delectus detraxit per cu. Ad pri vidit modus altera! In erat complectitur sit, quo no nostro insolens? Aliquam patrioque scribentur quo ad, partem commune eos at. Eius vivendo comprehensam has ne, sea ne eros mazim oratio. Soluta populo te duo, ne pro causae fabulas percipitur, feugiat.",
"user_id": 1
},
{
"title": "Python and Physics",
"content": "Agam mediocritatem sed ex, fabellas recusabo dissentias vix te. No principes consequat inciderint pri, ea mundi affert persecuti mea, ne usu veri regione nostrum! An tibique dissentiet referrentur pro, ridens temporibus eu est! Ius ne omnes affert rationibus, ut detraxit qualisque usu. Accusamus reformidans sea id?\r\n\r\nEu aliquip gloriatur mei. Qui ad sint scripserit? Te instructior definitiones mel, sale mutat everti at his. Ea mea quot recusabo philosophia. Et nam quod adipisci, quo atqui appetere recusabo id, detraxit inimicus vim.",
"user_id": 1
},
{
"title": "Just A Few More Healines Should Do It",
"content": "Duo at tibique commune vulputate, ex facilis tacimates disputationi mei. Mel eu inani prompta labores! Audire omnesque offendit ex eos. An ferri accusata his, vel agam habeo maiestatis ex, eam mutat iisque concludaturque ut. Ut tamquam minimum partiendo vim. An nam vidit doming graecis.\r\n\r\nSingulis abhorreant his in, et altera audiam feugiat mei. Pri eius dolor persequeris id! Nam ea dolorem expetendis, idque everti suscipit qui te, noster repudiare dignissim per ex? No vim iriure tibique comprehensam, per utamur consequat.",
"user_id": 1
},
{
"title": "Music To Listen To While Coding",
"content": "Feugait reprimique eu mel, te eum dico electram. Nam no nemore cotidieque. Vim cu suas atqui dicunt. Id labitur dissentiunt per, ignota maiorum pri no? Clita altera sanctus ex his!\r\n\r\nAt alia electram reprehendunt eam, sea te volumus quaestio. Commodo voluptua senserit ius ne, eu enim disputationi eam? Id pri omnium blandit, nullam denique nec no? Sapientem vituperata sit et, nisl facilisis periculis in est. Elaboraret accommodare id vel? Cibo eripuit ut has, sed cu liber invidunt.\r\n\r\nEi pro vide quas dolorum, sea no fugit sanctus neglegentur. Sit feugait disputationi ne. Id diceret periculis nec, sint nonumes in sea, cum.",
"user_id": 1
},
{
"title": "5 Tips for Writing Catchy Headlines",
"content": "Ea homero possit epicuri est, debitis docendi tacimates cu duo? Ad lorem cetero disputando pri, veniam eruditi tacimates per te.",
"user_id": 2
},
{
"title": "The Rise of Data Science",
"content": "Per omittam placerat at. Eius aeque ei mei. Usu ex partiendo salutandi. Pro illud placerat molestiae ex, habeo vidisse voluptatum cu vel, efficiendi accommodare eum ea! Ne has case minimum facilisis, pertinax efficiendi eu vel!\r\n\r\nEt movet semper assueverit his. Mei at liber vitae. Vix et periculis definiebas, vero falli.",
"user_id": 2
},
{
"title": "Best Videos For Learning Python",
"content": "Mei ei mazim dicunt feugait? Ludus mandamus ne est, per ne iusto facilisis moderatius! Has agam utamur ad! Ius reque aeterno cu, fabellas facilisi repudiare eu sit, te cibo convenire similique est. Ea cum viderer imperdiet liberavisse.\r\n\r\nPro minim iuvaret ad. No nam ornatus principes euripidis, at sale vituperatoribus eos, eros regione scripserit id mea. Has ne inermis nostrum, quo tantas melius dissentias at! Ut vim tibique omnesque. An mel modo ponderum, eum at probo appetere imperdiet? Natum quaeque intellegebat per ex. Cu viris clita sit?\r\n\r\nReque menandri dissentias sed ne, no tota nonumes eos, vix in tempor maiestatis erant.",
"user_id": 1
},
{
"title": "Top 10 Python Tips and Tricks",
"content": "Pro minim iuvaret ad. No nam ornatus principes euripidis, at sale vituperatoribus eos, eros regione scripserit id mea. Has ne inermis nostrum, quo tantas melius dissentias at! Ut vim tibique omnesque. An mel modo ponderum, eum at probo appetere imperdiet? Natum quaeque intellegebat per ex. Cu viris clita sit?\r\n\r\nReque menandri dissentias sed ne, no tota nonumes eos, vix in tempor maiestatis erant.",
"user_id": 1
},
{
"title": "Top 5 YouTube Channels For Learning Programming",
"content": "Quo inani quando ea, mel an vide adversarium suscipiantur. Et dicunt eleifend splendide pro. Nibh animal dolorem vim ex, nec te agam referrentur. Usu admodum ocurreret ne.\r\n\r\nEt dico audire cotidieque sed, cibo latine ut has, an case magna alienum.",
"user_id": 2
},
{
"title": "My Latest Updated Post",
"content": "Erat expetenda definitionem id eos. Semper suscipit eum ut, eum ex nemore copiosae. Nam probatus pertinacia eu! No alii voluptua abhorreant nec, te pro impedit concludaturque, in sea malis torquatos disputationi! Nam te alii nobis ponderum, ei fugit accusamus pro.\r\n\r\nCongue salutandi ex eam! Mei an prima consulatu, erat detracto eu quo? Vim ea esse utinam efficiantur, at noster dicunt.",
"user_id": 1
}
]
certifi==2018.10.15
chardet==3.0.4
Django==2.1
django-crispy-forms==1.7.2
idna==2.7
Pillow==5.2.0
pytz==2018.5
requests==2.19.1
urllib3==1.23
from django.contrib import admin
from .models import Profile
admin.site.register(Profile)
from django.apps import AppConfig
class UsersConfig(AppConfig):
name = 'users'
def ready(self):
import users.signals
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
from .models import Profile
class UserRegisterForm(UserCreationForm):
email = forms.EmailField()
class Meta:
model = User
fields = ['username', 'email', 'password1', 'password2']
class UserUpdateForm(forms.ModelForm):
email = forms.EmailField()
class Meta:
model = User
fields = ['username', 'email']
class ProfileUpdateForm(forms.ModelForm):
class Meta:
model = Profile
fields = ['image']
# Generated by Django 2.1 on 2018-08-28 21:29
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Profile',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('image', models.ImageField(default='default.jpg', upload_to='profile_pics')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
from django.db import models
from django.contrib.auth.models import User
from PIL import Image
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
image = models.ImageField(default='default.jpg', upload_to='profile_pics')
def __str__(self):
return f'{self.user.username} Profile'
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
img = Image.open(self.image.path)
if img.height > 300 or img.width > 300:
output_size = (300, 300)
img.thumbnail(output_size)
img.save(self.image.path)
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from django.dispatch import receiver
from .models import Profile
@receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_profile(sender, instance, **kwargs):
instance.profile.save()
{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="content-section">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Log In</legend>
{{ form|crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Login</button>
<small class="text-muted ml-2">
<a href="{% url 'password_reset' %}">Forgot Password?</a>
</small>
</div>
</form>
<div class="border-top pt-3">
<small class="text-muted">
Need An Account? <a class="ml-2" href="{% url 'register' %}">Sign Up Now</a>
</small>
</div>
</div>
{% endblock content %}
{% extends "blog/base.html" %}
{% block content %}
<h2>You have been logged out</h2>
<div class="border-top pt-3">
<small class="text-muted">
<a href="{% url 'login' %}">Log In Again</a>
</small>
</div>
{% endblock content %}
{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="content-section">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Reset Password</legend>
{{ form|crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Request Password Reset</button>
</div>
</form>
</div>
{% endblock content %}
{% extends "blog/base.html" %}
{% block content %}
<div class="alert alert-info">
Your password has been set.
</div>
<a href="{% url 'login' %}">Sign In Here</a>
{% endblock content %}
{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="content-section">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Reset Password</legend>
{{ form|crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Reset Password</button>
</div>
</form>
</div>
{% endblock content %}
{% extends "blog/base.html" %}
{% block content %}
<div class="alert alert-info">
An email has been sent with instructions to reset your password
</div>
{% endblock content %}
{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="content-section">
<div class="media">
<img class="rounded-circle account-img" src="{{ user.profile.image.url }}">
<div class="media-body">
<h2 class="account-heading">{{ user.username }}</h2>
<p class="text-secondary">{{ user.email }}</p>
</div>
</div>
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Profile Info</legend>
{{ u_form|crispy }}
{{ p_form|crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Update</button>
</div>
</form>
</div>
{% endblock content %}
{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="content-section">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Join Today</legend>
{{ form|crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Sign Up</button>
</div>
</form>
<div class="border-top pt-3">
<small class="text-muted">
Already Have An Account? <a class="ml-2" href="{% url 'login' %}">Sign In</a>
</small>
</div>
</div>
{% endblock content %}
from django.test import TestCase
# Create your tests here.
from django.shortcuts import render, redirect
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from .forms import UserRegisterForm, UserUpdateForm, ProfileUpdateForm
def register(request):
if request.method == 'POST':
form = UserRegisterForm(request.POST)
if form.is_valid():
form.save()
username = form.cleaned_data.get('username')
messages.success(request, f'Your account has been created! You are now able to log in')
return redirect('login')
else:
form = UserRegisterForm()
return render(request, 'users/register.html', {'form': form})
@login_required
def profile(request):
if request.method == 'POST':
u_form = UserUpdateForm(request.POST, instance=request.user)
p_form = ProfileUpdateForm(request.POST,
request.FILES,
instance=request.user.profile)
if u_form.is_valid() and p_form.is_valid():
u_form.save()
p_form.save()
messages.success(request, f'Your account has been updated!')
return redirect('profile')
else:
u_form = UserUpdateForm(instance=request.user)
p_form = ProfileUpdateForm(instance=request.user.profile)
context = {
'u_form': u_form,
'p_form': p_form
}
return render(request, 'users/profile.html', context)
certifi==2018.10.15
chardet==3.0.4
Django==2.1
django-crispy-forms==1.7.2
idna==2.7
Pillow==5.2.0
pytz==2018.5
requests==2.19.1
urllib3==1.23
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
from flask_login import LoginManager
from flask_mail import Mail
from flaskblog.config import Config
db = SQLAlchemy()
bcrypt = Bcrypt()
login_manager = LoginManager()
login_manager.login_view = 'users.login'
login_manager.login_message_category = 'info'
mail = Mail()
def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(Config)
db.init_app(app)
bcrypt.init_app(app)
login_manager.init_app(app)
mail.init_app(app)
from flaskblog.users.routes import users
from flaskblog.posts.routes import posts
from flaskblog.main.routes import main
from flaskblog.errors.handlers import errors
app.register_blueprint(users)
app.register_blueprint(posts)
app.register_blueprint(main)
app.register_blueprint(errors)
return app
import os
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY')
SQLALCHEMY_DATABASE_URI = os.environ.get('SQLALCHEMY_DATABASE_URI')
MAIL_SERVER = 'smtp.googlemail.com'
MAIL_PORT = 587
MAIL_USE_TLS = True
MAIL_USERNAME = os.environ.get('EMAIL_USER')
MAIL_PASSWORD = os.environ.get('EMAIL_PASS')
from flask import Blueprint, render_template
errors = Blueprint('errors', __name__)
@errors.app_errorhandler(404)
def error_404(error):
return render_template('errors/404.html'), 404
@errors.app_errorhandler(403)
def error_403(error):
return render_template('errors/403.html'), 403
@errors.app_errorhandler(500)
def error_500(error):
return render_template('errors/500.html'), 500
from flask import render_template, request, Blueprint
from flaskblog.models import Post
main = Blueprint('main', __name__)
@main.route("/")
@main.route("/home")
def home():
page = request.args.get('page', 1, type=int)
posts = Post.query.order_by(Post.date_posted.desc()).paginate(page=page, per_page=5)
return render_template('home.html', posts=posts)
@main.route("/about")
def about():
return render_template('about.html', title='About')
from datetime import datetime
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from flask import current_app
from flaskblog import db, login_manager
from flask_login import UserMixin
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
image_file = db.Column(db.String(20), nullable=False, default='default.jpg')
password = db.Column(db.String(60), nullable=False)
posts = db.relationship('Post', backref='author', lazy=True)
def get_reset_token(self, expires_sec=1800):
s = Serializer(current_app.config['SECRET_KEY'], expires_sec)
return s.dumps({'user_id': self.id}).decode('utf-8')
@staticmethod
def verify_reset_token(token):
s = Serializer(current_app.config['SECRET_KEY'])
try:
user_id = s.loads(token)['user_id']
except:
return None
return User.query.get(user_id)
def __repr__(self):
return f"User('{self.username}', '{self.email}', '{self.image_file}')"
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
content = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
def __repr__(self):
return f"Post('{self.title}', '{self.date_posted}')"
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, TextAreaField
from wtforms.validators import DataRequired
class PostForm(FlaskForm):
title = StringField('Title', validators=[DataRequired()])
content = TextAreaField('Content', validators=[DataRequired()])
submit = SubmitField('Post')
from flask import (render_template, url_for, flash,
redirect, request, abort, Blueprint)
from flask_login import current_user, login_required
from flaskblog import db
from flaskblog.models import Post
from flaskblog.posts.forms import PostForm
posts = Blueprint('posts', __name__)
@posts.route("/post/new", methods=['GET', 'POST'])
@login_required
def new_post():
form = PostForm()
if form.validate_on_submit():
post = Post(title=form.title.data, content=form.content.data, author=current_user)
db.session.add(post)
db.session.commit()
flash('Your post has been created!', 'success')
return redirect(url_for('main.home'))
return render_template('create_post.html', title='New Post',
form=form, legend='New Post')
@posts.route("/post/<int:post_id>")
def post(post_id):
post = Post.query.get_or_404(post_id)
return render_template('post.html', title=post.title, post=post)
@posts.route("/post/<int:post_id>/update", methods=['GET', 'POST'])
@login_required
def update_post(post_id):
post = Post.query.get_or_404(post_id)
if post.author != current_user:
abort(403)
form = PostForm()
if form.validate_on_submit():
post.title = form.title.data
post.content = form.content.data
db.session.commit()
flash('Your post has been updated!', 'success')
return redirect(url_for('posts.post', post_id=post.id))
elif request.method == 'GET':
form.title.data = post.title
form.content.data = post.content
return render_template('create_post.html', title='Update Post',
form=form, legend='Update Post')
@posts.route("/post/<int:post_id>/delete", methods=['POST'])
@login_required
def delete_post(post_id):
post = Post.query.get_or_404(post_id)
if post.author != current_user:
abort(403)
db.session.delete(post)
db.session.commit()
flash('Your post has been deleted!', 'success')
return redirect(url_for('main.home'))
body {
background: #fafafa;
color: #333333;
margin-top: 5rem;
}
h1, h2, h3, h4, h5, h6 {
color: #444444;
}
.bg-steel {
background-color: #5f788a;
}
.site-header .navbar-nav .nav-link {
color: #cbd5db;
}
.site-header .navbar-nav .nav-link:hover {
color: #ffffff;
}
.site-header .navbar-nav .nav-link.active {
font-weight: 500;
}
.content-section {
background: #ffffff;
padding: 10px 20px;
border: 1px solid #dddddd;
border-radius: 3px;
margin-bottom: 20px;
}
.article-title {
color: #444444;
}
a.article-title:hover {
color: #428bca;
text-decoration: none;
}
.article-content {
white-space: pre-line;
}
.article-img {
height: 65px;
width: 65px;
margin-right: 16px;
}
.article-metadata {
padding-bottom: 1px;
margin-bottom: 4px;
border-bottom: 1px solid #e3e3e3
}
.article-metadata a:hover {
color: #333;
text-decoration: none;
}
.article-svg {
width: 25px;
height: 25px;
vertical-align: middle;
}
.account-img {
height: 125px;
width: 125px;
margin-right: 20px;
margin-bottom: 16px;
}
.account-heading {
font-size: 2.5rem;
}
{% extends "layout.html" %}
{% block content %}
<h1>About Page</h1>
{% endblock content %}
{% extends "layout.html" %}
{% block content %}
<div class="content-section">
<div class="media">
<img class="rounded-circle account-img" src="{{ image_file }}">
<div class="media-body">
<h2 class="account-heading">{{ current_user.username }}</h2>
<p class="text-secondary">{{ current_user.email }}</p>
</div>
</div>
<form method="POST" action="" enctype="multipart/form-data">
{{ form.hidden_tag() }}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Account Info</legend>
<div class="form-group">
{{ form.username.label(class="form-control-label") }}
{% if form.username.errors %}
{{ form.username(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.username.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.username(class="form-control form-control-lg") }}
{% endif %}
</div>
<div class="form-group">
{{ form.email.label(class="form-control-label") }}
{% if form.email.errors %}
{{ form.email(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.email.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.email(class="form-control form-control-lg") }}
{% endif %}
</div>
<div class="form-group">
{{ form.picture.label() }}
{{ form.picture(class="form-control-file") }}
{% if form.picture.errors %}
{% for error in form.picture.errors %}
<span class="text-danger">{{ error }}</span></br>
{% endfor %}
{% endif %}
</div>
</fieldset>
<div class="form-group">
{{ form.submit(class="btn btn-outline-info") }}
</div>
</form>
</div>
{% endblock content %}
{% extends "layout.html" %}
{% block content %}
<div class="content-section">
<form method="POST" action="">
{{ form.hidden_tag() }}
<fieldset class="form-group">
<legend class="border-bottom mb-4">{{ legend }}</legend>
<div class="form-group">
{{ form.title.label(class="form-control-label") }}
{% if form.title.errors %}
{{ form.title(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.title.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.title(class="form-control form-control-lg") }}
{% endif %}
</div>
<div class="form-group">
{{ form.content.label(class="form-control-label") }}
{% if form.content.errors %}
{{ form.content(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.content.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.content(class="form-control form-control-lg") }}
{% endif %}
</div>
</fieldset>
<div class="form-group">
{{ form.submit(class="btn btn-outline-info") }}
</div>
</form>
</div>
{% endblock content %}
{% extends "layout.html" %}
{% block content %}
<div class="content-section">
<h1>You don't have permission to do that (403)</h1>
<p>Please check your account and try again</p>
</div>
{% endblock content %}
{% extends "layout.html" %}
{% block content %}
<div class="content-section">
<h1>Oops. Page Not Found (404)</h1>
<p>That page does not exist. Please try a different location</p>
</div>
{% endblock content %}
{% extends "layout.html" %}
{% block content %}
<div class="content-section">
<h1>Something went wrong (500)</h1>
<p>We're experiencing some trouble on our end. Please try again in the near future</p>
</div>
{% endblock content %}
{% extends "layout.html" %}
{% block content %}
{% for post in posts.items %}
<article class="media content-section">
<img class="rounded-circle article-img" src="{{ url_for('static', filename='profile_pics/' + post.author.image_file) }}">
<div class="media-body">
<div class="article-metadata">
<a class="mr-2" href="{{ url_for('users.user_posts', username=post.author.username) }}">{{ post.author.username }}</a>
<small class="text-muted">{{ post.date_posted.strftime('%Y-%m-%d') }}</small>
</div>
<h2><a class="article-title" href="{{ url_for('posts.post', post_id=post.id) }}">{{ post.title }}</a></h2>
<p class="article-content">{{ post.content }}</p>
</div>
</article>
{% endfor %}
{% for page_num in posts.iter_pages(left_edge=1, right_edge=1, left_current=1, right_current=2) %}
{% if page_num %}
{% if posts.page == page_num %}
<a class="btn btn-info mb-4" href="{{ url_for('main.home', page=page_num) }}">{{ page_num }}</a>
{% else %}
<a class="btn btn-outline-info mb-4" href="{{ url_for('main.home', page=page_num) }}">{{ page_num }}</a>
{% endif %}
{% else %}
...
{% endif %}
{% endfor %}
{% endblock content %}
<!DOCTYPE html>
<html>
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='main.css') }}">
{% if title %}
<title>Flask Blog - {{ title }}</title>
{% else %}
<title>Flask Blog</title>
{% endif %}
</head>
<body>
<header class="site-header">
<nav class="navbar navbar-expand-md navbar-dark bg-steel fixed-top">
<div class="container">
<a class="navbar-brand mr-4" href="/">Flask Blog</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarToggle" aria-controls="navbarToggle" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarToggle">
<div class="navbar-nav mr-auto">
<a class="nav-item nav-link" href="{{ url_for('main.home') }}">Home</a>
<a class="nav-item nav-link" href="{{ url_for('main.about') }}">About</a>
</div>
<!-- Navbar Right Side -->
<div class="navbar-nav">
{% if current_user.is_authenticated %}
<a class="nav-item nav-link" href="{{ url_for('posts.new_post') }}">New Post</a>
<a class="nav-item nav-link" href="{{ url_for('users.account') }}">Account</a>
<a class="nav-item nav-link" href="{{ url_for('users.logout') }}">Logout</a>
{% else %}
<a class="nav-item nav-link" href="{{ url_for('users.login') }}">Login</a>
<a class="nav-item nav-link" href="{{ url_for('users.register') }}">Register</a>
{% endif %}
</div>
</div>
</div>
</nav>
</header>
<main role="main" class="container">
<div class="row">
<div class="col-md-8">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
<div class="col-md-4">
<div class="content-section">
<h3>Our Sidebar</h3>
<p class='text-muted'>You can put any information here you'd like.
<ul class="list-group">
<li class="list-group-item list-group-item-light">Latest Posts</li>
<li class="list-group-item list-group-item-light">Announcements</li>
<li class="list-group-item list-group-item-light">Calendars</li>
<li class="list-group-item list-group-item-light">etc</li>
</ul>
</p>
</div>
</div>
</div>
</main>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
</body>
</html>
{% extends "layout.html" %}
{% block content %}
<div class="content-section">
<form method="POST" action="">
{{ form.hidden_tag() }}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Log In</legend>
<div class="form-group">
{{ form.email.label(class="form-control-label") }}
{% if form.email.errors %}
{{ form.email(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.email.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.email(class="form-control form-control-lg") }}
{% endif %}
</div>
<div class="form-group">
{{ form.password.label(class="form-control-label") }}
{% if form.password.errors %}
{{ form.password(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.password.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.password(class="form-control form-control-lg") }}
{% endif %}
</div>
<div class="form-check">
{{ form.remember(class="form-check-input") }}
{{ form.remember.label(class="form-check-label") }}
</div>
</fieldset>
<div class="form-group">
{{ form.submit(class="btn btn-outline-info") }}
<small class="text-muted ml-2">
<a href="{{ url_for('users.reset_request') }}">Forgot Password?</a>
</small>
</div>
</form>
</div>
<div class="border-top pt-3">
<small class="text-muted">
Need An Account? <a class="ml-2" href="{{ url_for('users.register') }}">Sign Up Now</a>
</small>
</div>
{% endblock content %}
{% extends "layout.html" %}
{% block content %}
<article class="media content-section">
<img class="rounded-circle article-img" src="{{ url_for('static', filename='profile_pics/' + post.author.image_file) }}">
<div class="media-body">
<div class="article-metadata">
<a class="mr-2" href="{{ url_for('users.user_posts', username=post.author.username) }}">{{ post.author.username }}</a>
<small class="text-muted">{{ post.date_posted.strftime('%Y-%m-%d') }}</small>
{% if post.author == current_user %}
<div>
<a class="btn btn-secondary btn-sm mt-1 mb-1" href="{{ url_for('posts.update_post', post_id=post.id) }}">Update</a>
<button type="button" class="btn btn-danger btn-sm m-1" data-toggle="modal" data-target="#deleteModal">Delete</button>
</div>
{% endif %}
</div>
<h2 class="article-title">{{ post.title }}</h2>
<p class="article-content">{{ post.content }}</p>
</div>
</article>
<!-- Modal -->
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="deleteModalLabel">Delete Post?</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<form action="{{ url_for('posts.delete_post', post_id=post.id) }}" method="POST">
<input class="btn btn-danger" type="submit" value="Delete">
</form>
</div>
</div>
</div>
</div>
{% endblock content %}
{% extends "layout.html" %}
{% block content %}
<div class="content-section">
<form method="POST" action="">
{{ form.hidden_tag() }}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Join Today</legend>
<div class="form-group">
{{ form.username.label(class="form-control-label") }}
{% if form.username.errors %}
{{ form.username(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.username.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.username(class="form-control form-control-lg") }}
{% endif %}
</div>
<div class="form-group">
{{ form.email.label(class="form-control-label") }}
{% if form.email.errors %}
{{ form.email(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.email.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.email(class="form-control form-control-lg") }}
{% endif %}
</div>
<div class="form-group">
{{ form.password.label(class="form-control-label") }}
{% if form.password.errors %}
{{ form.password(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.password.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.password(class="form-control form-control-lg") }}
{% endif %}
</div>
<div class="form-group">
{{ form.confirm_password.label(class="form-control-label") }}
{% if form.confirm_password.errors %}
{{ form.confirm_password(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.confirm_password.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.confirm_password(class="form-control form-control-lg") }}
{% endif %}
</div>
</fieldset>
<div class="form-group">
{{ form.submit(class="btn btn-outline-info") }}
</div>
</form>
</div>
<div class="border-top pt-3">
<small class="text-muted">
Already Have An Account? <a class="ml-2" href="{{ url_for('users.login') }}">Sign In</a>
</small>
</div>
{% endblock content %}
{% extends "layout.html" %}
{% block content %}
<div class="content-section">
<form method="POST" action="">
{{ form.hidden_tag() }}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Reset Password</legend>
<div class="form-group">
{{ form.email.label(class="form-control-label") }}
{% if form.email.errors %}
{{ form.email(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.email.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.email(class="form-control form-control-lg") }}
{% endif %}
</div>
</fieldset>
<div class="form-group">
{{ form.submit(class="btn btn-outline-info") }}
</div>
</form>
</div>
{% endblock content %}
{% extends "layout.html" %}
{% block content %}
<div class="content-section">
<form method="POST" action="">
{{ form.hidden_tag() }}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Reset Password</legend>
<div class="form-group">
{{ form.password.label(class="form-control-label") }}
{% if form.password.errors %}
{{ form.password(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.password.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.password(class="form-control form-control-lg") }}
{% endif %}
</div>
<div class="form-group">
{{ form.confirm_password.label(class="form-control-label") }}
{% if form.confirm_password.errors %}
{{ form.confirm_password(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.confirm_password.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.confirm_password(class="form-control form-control-lg") }}
{% endif %}
</div>
</fieldset>
<div class="form-group">
{{ form.submit(class="btn btn-outline-info") }}
</div>
</form>
</div>
{% endblock content %}
{% extends "layout.html" %}
{% block content %}
<h1 class="mb-3">Posts by {{ user.username }} ({{ posts.total }})</h1>
{% for post in posts.items %}
<article class="media content-section">
<img class="rounded-circle article-img" src="{{ url_for('static', filename='profile_pics/' + post.author.image_file) }}">
<div class="media-body">
<div class="article-metadata">
<a class="mr-2" href="{{ url_for('users.user_posts', username=post.author.username) }}">{{ post.author.username }}</a>
<small class="text-muted">{{ post.date_posted.strftime('%Y-%m-%d') }}</small>
</div>
<h2><a class="article-title" href="{{ url_for('posts.post', post_id=post.id) }}">{{ post.title }}</a></h2>
<p class="article-content">{{ post.content }}</p>
</div>
</article>
{% endfor %}
{% for page_num in posts.iter_pages(left_edge=1, right_edge=1, left_current=1, right_current=2) %}
{% if page_num %}
{% if posts.page == page_num %}
<a class="btn btn-info mb-4" href="{{ url_for('users.user_posts', username=user.username, page=page_num) }}">{{ page_num }}</a>
{% else %}
<a class="btn btn-outline-info mb-4" href="{{ url_for('users.user_posts', username=user.username, page=page_num) }}">{{ page_num }}</a>
{% endif %}
{% else %}
...
{% endif %}
{% endfor %}
{% endblock content %}
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed
from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError
from flask_login import current_user
from flaskblog.models import User
class RegistrationForm(FlaskForm):
username = StringField('Username',
validators=[DataRequired(), Length(min=2, max=20)])
email = StringField('Email',
validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
confirm_password = PasswordField('Confirm Password',
validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('Sign Up')
def validate_username(self, username):
user = User.query.filter_by(username=username.data).first()
if user:
raise ValidationError('That username is taken. Please choose a different one.')
def validate_email(self, email):
user = User.query.filter_by(email=email.data).first()
if user:
raise ValidationError('That email is taken. Please choose a different one.')
class LoginForm(FlaskForm):
email = StringField('Email',
validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
remember = BooleanField('Remember Me')
submit = SubmitField('Login')
class UpdateAccountForm(FlaskForm):
username = StringField('Username',
validators=[DataRequired(), Length(min=2, max=20)])
email = StringField('Email',
validators=[DataRequired(), Email()])
picture = FileField('Update Profile Picture', validators=[FileAllowed(['jpg', 'png'])])
submit = SubmitField('Update')
def validate_username(self, username):
if username.data != current_user.username:
user = User.query.filter_by(username=username.data).first()
if user:
raise ValidationError('That username is taken. Please choose a different one.')
def validate_email(self, email):
if email.data != current_user.email:
user = User.query.filter_by(email=email.data).first()
if user:
raise ValidationError('That email is taken. Please choose a different one.')
class RequestResetForm(FlaskForm):
email = StringField('Email',
validators=[DataRequired(), Email()])
submit = SubmitField('Request Password Reset')
def validate_email(self, email):
user = User.query.filter_by(email=email.data).first()
if user is None:
raise ValidationError('There is no account with that email. You must register first.')
class ResetPasswordForm(FlaskForm):
password = PasswordField('Password', validators=[DataRequired()])
confirm_password = PasswordField('Confirm Password',
validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('Reset Password')
from flask import render_template, url_for, flash, redirect, request, Blueprint
from flask_login import login_user, current_user, logout_user, login_required
from flaskblog import db, bcrypt
from flaskblog.models import User, Post
from flaskblog.users.forms import (RegistrationForm, LoginForm, UpdateAccountForm,
RequestResetForm, ResetPasswordForm)
from flaskblog.users.utils import save_picture, send_reset_email
users = Blueprint('users', __name__)
@users.route("/register", methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('main.home'))
form = RegistrationForm()
if form.validate_on_submit():
hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
user = User(username=form.username.data, email=form.email.data, password=hashed_password)
db.session.add(user)
db.session.commit()
flash('Your account has been created! You are now able to log in', 'success')
return redirect(url_for('users.login'))
return render_template('register.html', title='Register', form=form)
@users.route("/login", methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('main.home'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user and bcrypt.check_password_hash(user.password, form.password.data):
login_user(user, remember=form.remember.data)
next_page = request.args.get('next')
return redirect(next_page) if next_page else redirect(url_for('main.home'))
else:
flash('Login Unsuccessful. Please check email and password', 'danger')
return render_template('login.html', title='Login', form=form)
@users.route("/logout")
def logout():
logout_user()
return redirect(url_for('main.home'))
@users.route("/account", methods=['GET', 'POST'])
@login_required
def account():
form = UpdateAccountForm()
if form.validate_on_submit():
if form.picture.data:
picture_file = save_picture(form.picture.data)
current_user.image_file = picture_file
current_user.username = form.username.data
current_user.email = form.email.data
db.session.commit()
flash('Your account has been updated!', 'success')
return redirect(url_for('users.account'))
elif request.method == 'GET':
form.username.data = current_user.username
form.email.data = current_user.email
image_file = url_for('static', filename='profile_pics/' + current_user.image_file)
return render_template('account.html', title='Account',
image_file=image_file, form=form)
@users.route("/user/<string:username>")
def user_posts(username):
page = request.args.get('page', 1, type=int)
user = User.query.filter_by(username=username).first_or_404()
posts = Post.query.filter_by(author=user)\
.order_by(Post.date_posted.desc())\
.paginate(page=page, per_page=5)
return render_template('user_posts.html', posts=posts, user=user)
@users.route("/reset_password", methods=['GET', 'POST'])
def reset_request():
if current_user.is_authenticated:
return redirect(url_for('main.home'))
form = RequestResetForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
send_reset_email(user)
flash('An email has been sent with instructions to reset your password.', 'info')
return redirect(url_for('users.login'))
return render_template('reset_request.html', title='Reset Password', form=form)
@users.route("/reset_password/<token>", methods=['GET', 'POST'])
def reset_token(token):
if current_user.is_authenticated:
return redirect(url_for('main.home'))
user = User.verify_reset_token(token)
if user is None:
flash('That is an invalid or expired token', 'warning')
return redirect(url_for('users.reset_request'))
form = ResetPasswordForm()
if form.validate_on_submit():
hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
user.password = hashed_password
db.session.commit()
flash('Your password has been updated! You are now able to log in', 'success')
return redirect(url_for('users.login'))
return render_template('reset_token.html', title='Reset Password', form=form)
import os
import secrets
from PIL import Image
from flask import url_for, current_app
from flask_mail import Message
from flaskblog import mail
def save_picture(form_picture):
random_hex = secrets.token_hex(8)
_, f_ext = os.path.splitext(form_picture.filename)
picture_fn = random_hex + f_ext
picture_path = os.path.join(current_app.root_path, 'static/profile_pics', picture_fn)
output_size = (125, 125)
i = Image.open(form_picture)
i.thumbnail(output_size)
i.save(picture_path)
return picture_fn
def send_reset_email(user):
token = user.get_reset_token()
msg = Message('Password Reset Request',
sender='noreply@demo.com',
recipients=[user.email])
msg.body = f'''To reset your password, visit the following link:
{url_for('users.reset_token', token=token, _external=True)}
If you did not make this request then simply ignore this email and no changes will be made.
'''
mail.send(msg)
bcrypt==3.1.4
blinker==1.4
certifi==2016.2.28
cffi==1.11.5
click==6.7
Flask==1.0
Flask-Bcrypt==0.7.1
Flask-Login==0.4.1
Flask-Mail==0.9.1
Flask-SQLAlchemy==2.3.2
Flask-WTF==0.14.2
itsdangerous==0.24
Jinja2==2.10
MarkupSafe==1.0
Pillow==5.3.0
pycparser==2.18
six==1.11.0
SQLAlchemy==1.2.7
Werkzeug==0.14.1
WTForms==2.1
from flaskblog import create_app
app = create_app()
if __name__ == '__main__':
app.run(debug=True)
bcrypt==3.1.4
blinker==1.4
certifi==2016.2.28
cffi==1.11.5
click==6.7
Flask==1.0
Flask-Bcrypt==0.7.1
Flask-Login==0.4.1
Flask-Mail==0.9.1
Flask-SQLAlchemy==2.3.2
Flask-WTF==0.14.2
itsdangerous==0.24
Jinja2==2.10
MarkupSafe==1.0
Pillow==5.3.0
pycparser==2.18
six==1.11.0
SQLAlchemy==1.2.7
Werkzeug==0.14.1
WTForms==2.1
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册