import from kontor-flask

This commit is contained in:
Thomas Peetz
2025-01-08 22:31:20 +01:00
committed by Thomas Peetz
parent d6410e2584
commit 0d2f27f771
61 changed files with 4296 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
# config.py
class Config(object):
"""
Common configurations
"""
# Put any configurations here that are common across all environments
host = '127.0.0.1'
port = 8500
database = 'kontor'
class DevelopmentConfig(Config):
"""
Development configurations
"""
DEBUG = True
host = '0.0.0.0'
database = 'kontor_dev'
class ProductionConfig(Config):
"""
Production configurations
"""
DEBUG = False
class TestingConfig(Config):
"""
Testing configurations
"""
TESTING = True
DEBUG = True
port = 8600
database = 'kontor_test'
app_config = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'testing': TestingConfig
}
+20
View File
@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = KontorFlask
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+200
View File
@@ -0,0 +1,200 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Kontor Flask documentation build configuration file, created by
# sphinx-quickstart on Mon Nov 6 08:53:04 2017.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = ['sphinx.ext.autodoc',
'sphinx.ext.doctest',
'sphinx.ext.intersphinx',
'sphinx.ext.todo',
'sphinx.ext.coverage',
'sphinx.ext.mathjax',
'sphinx.ext.ifconfig',
'sphinx.ext.viewcode']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = 'Kontor Flask'
copyright = '2017, Thomas Peetz'
author = 'Thomas Peetz'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '0.1'
# The full version, including alpha/beta/rc tags.
release = '0.0.7'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = 'de'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# This is required for the alabaster theme
# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
html_sidebars = {
'**': [
'relations.html', # needs 'show_related': True theme option to display
'searchbox.html',
]
}
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'KontorFlaskdoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'KontorFlask.tex', 'Kontor Flask Documentation',
'Thomas Peetz', 'manual'),
]
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'kontorflask', 'Kontor Flask Documentation',
[author], 1)
]
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'KontorFlask', 'Kontor Flask Documentation',
author, 'KontorFlask', 'One line description of project.',
'Miscellaneous'),
]
# -- Options for Epub output ----------------------------------------------
# Bibliographic Dublin Core info.
epub_title = project
epub_author = author
epub_publisher = author
epub_copyright = copyright
# The unique identifier of the text. This can be a ISBN number
# or the project homepage.
#
# epub_identifier = ''
# A unique identification for the text.
#
# epub_uid = ''
# A list of files that should not be packed into the epub file.
epub_exclude_files = ['search.html']
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'https://docs.python.org/': None}
+20
View File
@@ -0,0 +1,20 @@
.. Kontor Flask documentation master file, created by
sphinx-quickstart on Mon Nov 6 08:53:04 2017.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to Kontor Flask's documentation!
========================================
.. toctree::
:maxdepth: 2
:caption: Contents:
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
+74
View File
@@ -0,0 +1,74 @@
"""
Module kontor implements Kontor application.
"""
from flask import Flask
from flask_bootstrap import Bootstrap
from flask_login import LoginManager
from pymodm import connect
# local imports
from config import app_config
from .version import __version__
_LOGIN_MANAGER_ = LoginManager()
def get_host(config_name):
"""
Returns host address from configuration.
:param config_name:
:return: host address
"""
host = app_config[config_name].host
return host
def get_port(config_name):
"""
Returns port number from configuration.
:param config_name:
:return: port number
"""
port = app_config[config_name].port
return port
def create_app(config_name):
"""
Returns Flask application.
:param config_name:
:return: Flask application
"""
app = Flask(__name__, instance_relative_config=True)
app.config.from_object(app_config[config_name])
app.config.from_pyfile('config.py')
database = "mongodb://localhost:27017/{}".format(app_config[config_name].database)
connect(database, alias="kontor")
Bootstrap(app)
_LOGIN_MANAGER_.init_app(app)
_LOGIN_MANAGER_.login_message = "You must be logged in to access this page."
_LOGIN_MANAGER_.login_view = "auth.login"
from .admin.views import ADMIN as ADMIN_BLUEPRINT
app.register_blueprint(ADMIN_BLUEPRINT, url_prefix='/admin')
from .auth.views import AUTH as AUTH_BLUEPRINT
app.register_blueprint(AUTH_BLUEPRINT)
from .home.views import HOME as HOME_BLUEPRINT
app.register_blueprint(HOME_BLUEPRINT)
from .comics.views import COMIC as COMIC_BLUEPRINT
app.register_blueprint(COMIC_BLUEPRINT, url_prefix='/comics')
from .library.views import LIBRARY as LIBRARY_BLUEPRINT
app.register_blueprint(LIBRARY_BLUEPRINT, url_prefix='/library')
from .tysc.views import TYSC as TYSC_BLUEPRINT
app.register_blueprint(TYSC_BLUEPRINT, url_prefix='/tysc')
return app
+4
View File
@@ -0,0 +1,4 @@
"""
Module admin implements administration functions.
"""
from . import views
+17
View File
@@ -0,0 +1,17 @@
"""
Define form to edit users
"""
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
class UserEditForm(FlaskForm):
"""
Form for admin to edit users
"""
email = StringField('Email', validators=[DataRequired()])
username = StringField('Username', validators=[DataRequired()])
first_name = StringField('First Name', validators=[DataRequired()])
last_name = StringField('Last Name', validators=[DataRequired()])
submit = SubmitField('Submit')
+63
View File
@@ -0,0 +1,63 @@
"""
Define routing rules for managing users
"""
from flask import abort, Blueprint, flash, redirect, render_template, url_for
from flask_login import current_user, login_required
from .forms import UserEditForm
from ..models import User
ADMIN = Blueprint('admin', __name__)
def check_admin():
"""
Prevent non-admins from accessing the page
"""
if not current_user.is_admin:
abort(403)
# User Views
@ADMIN.route('/users')
@login_required
def list_users():
"""
List all users
"""
check_admin()
users = User.objects.all()
return render_template('admin/users/users.html',
users=users, title='Users')
@ADMIN.route('/users/edit/<user_id>', methods=['GET', 'POST'])
@login_required
def edit_user(user_id):
"""
Edit an user.
"""
check_admin()
user = User.objects.get({'username': user_id})
# prevent admin from being assigned a department or role
if user.is_admin:
abort(403)
form = UserEditForm(obj=user)
if form.validate_on_submit():
user.email = form.email.data
user.username = form.username.data
user.first_name = form.first_name.data
user.last_name = form.last_name.data
user.save()
flash('You have successfully edited an user.')
# redirect to the roles page
return redirect(url_for('admin.list_users'))
return render_template('admin/users/user.html',
user=user, form=form,
title='Edit User')
+4
View File
@@ -0,0 +1,4 @@
"""
Module auth implements authentication functions.
"""
from . import views
+50
View File
@@ -0,0 +1,50 @@
"""
Contains forms for authentication.
"""
from flask_wtf import FlaskForm
from wtforms import PasswordField, StringField, SubmitField, ValidationError
from wtforms.validators import DataRequired, Email, EqualTo
from ..models import User
class RegistrationForm(FlaskForm):
"""
Form for users to create new account
"""
email = StringField('Email', validators=[DataRequired(), Email()])
username = StringField('Username', validators=[DataRequired()])
first_name = StringField('First Name', validators=[DataRequired()])
last_name = StringField('Last Name', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired(),
EqualTo('confirm_password')
])
confirm_password = PasswordField('Confirm Password')
submit = SubmitField('Register')
def validate_email(self, field):
"""
Check if email is already in use.
:param field:
:return:
"""
if User.objects.raw({'email': field.data}).count() > 0:
raise ValidationError('Email is already in use.')
def validate_username(self, field):
"""
check if username is already in use.
:param field:
:return:
"""
if User.objects.raw({'username': field.data}).count() > 0:
raise ValidationError('Username is already in use.')
class LoginForm(FlaskForm):
"""
Form for users to login
"""
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
submit = SubmitField('Login')
+79
View File
@@ -0,0 +1,79 @@
"""
Define routing rules for registering users and login and logout.
"""
from flask import Blueprint, flash, redirect, render_template, url_for
from flask_login import login_required, login_user, logout_user
from .forms import LoginForm, RegistrationForm
from ..models import User
AUTH = Blueprint('auth', __name__)
@AUTH.route('/register', methods=['GET', 'POST'])
def register():
"""
Handle requests to the /register route
Add an employee to the database through the registration form
"""
form = RegistrationForm()
if form.validate_on_submit():
user = User()
user.email = form.email.data
user.username = form.username.data
user.first_name = form.first_name.data
user.last_name = form.last_name.data
user.password = form.password.data
# add employee to the database
user.save()
flash('You have successfully registered! You may now login.')
# redirect to the login page
return redirect(url_for('auth.login'))
# load registration template
return render_template('auth/register.html', form=form, title='Register')
@AUTH.route('/login', methods=['GET', 'POST'])
def login():
"""
Handle requests to the /login route
Validate given email with matching password
:return:
"""
form = LoginForm()
if form.validate_on_submit():
# check whether employee exists in the database and whether
# the password entered matches the password in the database
user = User.objects.get({'email': form.email.data})
if user is not None and user.verify_password(
form.password.data):
# log employee in
login_user(user)
# redirect to the appropriate dashboard page
if user.is_admin:
return redirect(url_for('home.admin_dashboard'))
else:
return redirect(url_for('home.dashboard'))
# when login details are incorrect
else:
flash('Invalid email or password.')
# load login template
return render_template('auth/login.html', form=form, title='Login')
@AUTH.route('/logout')
@login_required
def logout():
"""
Handle requests to the /logout route
Log an employee out through the logout link
"""
logout_user()
flash('You have successfully been logged out.')
# redirect to the login page
return redirect(url_for('auth.login'))
+3
View File
@@ -0,0 +1,3 @@
"""
Define routing rules for comic related information
"""
+31
View File
@@ -0,0 +1,31 @@
"""Define form to edit publisher, artists and comics"""
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, BooleanField, SelectField
from wtforms.validators import DataRequired
from bson import ObjectId
class PublisherForm(FlaskForm):
"""
Form to add and edit a Comic publisher
"""
name = StringField('Name', validators=[DataRequired()])
submit = SubmitField('Submit')
class ArtistForm(FlaskForm):
"""
Form to add and edit a Comic publisher
"""
name = StringField('Name', validators=[DataRequired()])
submit = SubmitField('Submit')
class ComicForm(FlaskForm):
"""
Form to add and edit Comics
"""
title = StringField('Title', validators=[DataRequired()])
publisher = SelectField('Publisher', coerce=ObjectId)
current_order = BooleanField('Current Order')
submit = SubmitField('Submit')
+51
View File
@@ -0,0 +1,51 @@
"""This modules declares the model for Comic related information."""
from flask import current_app
from pymongo.write_concern import WriteConcern
from pymodm import MongoModel, fields
class Publisher(MongoModel):
"""Class Publisher represents a publisher of a comic."""
name = fields.CharField()
def __str__(self):
return "Publisher({})".format(self.name)
@property
def comics(self):
"""
Return list of comics which has reference to this publisher
:return:
"""
comics = Comic.objects.raw({'publisher': self.pk})
current_app.logger.debug(comics)
return comics
class Meta:
"""Sets the connection and connections details."""
connection_alias = 'kontor'
write_concern = WriteConcern(j=True)
class Artist(MongoModel):
"""Class Artist represents a comic artist."""
name = fields.CharField()
class Meta:
"""Sets the connection and connections details."""
connection_alias = 'kontor'
write_concern = WriteConcern(j=True)
class Comic(MongoModel):
"""Class Comic represents a comic."""
title = fields.CharField()
publisher = fields.ReferenceField(Publisher)
current_order = fields.BooleanField(default=False)
completed = fields.BooleanField(default=False)
class Meta:
"""Sets the connection and connections details."""
connection_alias = 'kontor'
write_concern = WriteConcern(j=True)
+222
View File
@@ -0,0 +1,222 @@
"""
Define routing rules for comics, publisher and artists
"""
from flask import Blueprint, flash, redirect, render_template, url_for
from flask_login import login_required
from bson import ObjectId
from pymongo.errors import PyMongoError
from .forms import ComicForm, PublisherForm, ArtistForm
from .models import Comic, Publisher, Artist
COMIC = Blueprint('comic', __name__)
@COMIC.route('/artists')
@login_required
def list_artists():
"""
List all artists
:return:
"""
artists = Artist.objects.all()
return render_template('comics/artists.html',
artists=artists, title="Artists")
@COMIC.route('/artists/edit/<artist_id>', methods=['GET', 'POST'])
@login_required
def edit_artist(artist_id):
"""
Edit a comic artist
"""
artist = Artist.objects.get({'_id': ObjectId(artist_id)})
form = ArtistForm(obj=artist)
if form.validate_on_submit():
artist.name = form.name.data
artist.save()
flash('You have successfully edited the artist.')
return redirect(url_for('comic.list_artists'))
form.name.data = artist.name
return render_template('simpleform.html', action="Edit",
form=form, title="Edit Artist")
@COMIC.route('/artists/add', methods=['GET', 'POST'])
@login_required
def add_artist():
"""
Add a artist
:return:
"""
form = ArtistForm()
if form.validate_on_submit():
artist = Artist()
artist.name = form.name.data
try:
# add publisher to the database
artist.save()
flash('You have successfully added a new artist.')
except PyMongoError:
# in case publisher name already exists
flash('Error: artist name already exists.')
return redirect(url_for('comic.list_artists'))
return render_template('simpleform.html', action="Add",
form=form, title="Add Artist")
@COMIC.route('/artists/delete/<artist_id>', methods=['GET', 'POST'])
@login_required
def delete_artist(artist_id):
"""
Delete a comic artist
:param artist_id:
:return:
"""
artist = Artist.objects.raw({'_id': ObjectId(artist_id)})
if artist:
artist.delete()
flash('You have successfully deleted the comic artist.')
return redirect(url_for('comic.list_artists'))
@COMIC.route('/publishers')
@login_required
def list_publishers():
"""
List all publishers
:return:
"""
publishers = Publisher.objects.all()
return render_template('comics/publishers.html',
publishers=publishers, title="Publishers")
@COMIC.route('/publishers/add', methods=['GET', 'POST'])
@login_required
def add_publisher():
"""
Add a publisher to the database
:return:
"""
form = PublisherForm()
if form.validate_on_submit():
publisher = Publisher()
publisher.name = form.name.data
try:
# add publisher to the database
publisher.save()
flash('You have successfully added a new publisher.')
except PyMongoError:
# in case publisher name already exists
flash('Error: publisher name already exists.')
return redirect(url_for('comic.list_publishers'))
return render_template('simpleform.html', action="Add",
form=form, title="Add Publisher")
@COMIC.route('/publishers/edit/<publisher_id>', methods=['GET', 'POST'])
@login_required
def edit_publisher(publisher_id):
"""
Edit a publisher
"""
publisher = Publisher.objects.get({'_id': ObjectId(publisher_id)})
form = PublisherForm(obj=publisher)
if form.validate_on_submit():
publisher.name = form.name.data
publisher.save()
flash('You have successfully edited the publisher.')
return redirect(url_for('comic.list_publishers'))
form.name.data = publisher.name
return render_template('simpleform.html', action="Edit",
form=form, title="Edit Publisher")
@COMIC.route('/publishers/delete/<publisher_id>', methods=['GET', 'POST'])
@login_required
def delete_publisher(publisher_id):
"""
Delete a publisher
:param publisher_id: ObjectId of publisher
:return:
"""
publisher = Publisher.objects.raw({'_id': ObjectId(publisher_id)})
if publisher:
publisher.delete()
flash('You have successfully deleted the publisher.')
return redirect(url_for('comic.list_publishers'))
@COMIC.route('/comics')
@login_required
def list_comics():
"""
List all comics
:return:
"""
comics = Comic.objects.all()
return render_template('comics/comics.html',
comics=comics, title="Comics")
@COMIC.route('/comics/edit/<comic_id>', methods=['GET', 'POST'])
@login_required
def edit_comic(comic_id):
"""
Edit a comic
"""
comic = Comic.objects.get({'_id': ObjectId(comic_id)})
form = ComicForm(obj=comic)
form.publisher.choices = [(p.pk, p.name) for p in Publisher.objects.all()]
form.publisher.default = comic.publisher.pk
form.publisher.process_data(comic.publisher.pk)
if form.validate_on_submit():
comic.title = form.title.data
comic.current_order = form.current_order.data
comic.publisher = form.publisher.data
comic.save()
flash('You have successfully edited the comic.')
return redirect(url_for('comic.list_comics'))
form.title.data = comic.title
form.current_order.data = comic.current_order
return render_template('simpleform.html', action="Edit",
form=form, title="Edit Comic")
@COMIC.route('/comics/add', methods=['GET', 'POST'])
@login_required
def add_comic():
"""
Add a comic
:return:
"""
form = ComicForm()
form.publisher.choices = [(p.pk, p.name) for p in Publisher.objects.all()]
if form.validate_on_submit():
comic = Comic()
comic.title = form.title.data
comic.publisher = form.publisher.data
try:
comic.save()
flash('You have successfully added a new comic.')
except PyMongoError:
flash('Error: comic title already exists.')
return redirect(url_for('comic.list_comics'))
return render_template('simpleform.html', action="Add",
form=form, title="Add Comic")
@COMIC.route('/comics/delete/<comic_id>', methods=['GET', 'POST'])
@login_required
def delete_comic(comic_id):
"""
Delete a comic
:param comic_id:
:return:
"""
comic = Comic.objects.raw({'_id': ObjectId(comic_id)})
if comic:
comic.delete()
flash('You have successfully deleted the comic.')
return redirect(url_for('comic.list_comics'))
+3
View File
@@ -0,0 +1,3 @@
"""
Module for the Kontor homepage.
"""
+39
View File
@@ -0,0 +1,39 @@
"""
Define routing rules for homepage and dashboards
"""
from flask import Blueprint, render_template, abort
from flask_login import login_required, current_user
HOME = Blueprint('home', __name__)
@HOME.route('/')
def homepage():
"""
Render the homepage template on the / route
"""
return render_template('home/index.html', title="Welcome")
@HOME.route('/dashboard')
@login_required
def dashboard():
"""
Render the dashboard template on the /dashboard route
"""
return render_template('home/dashboard.html', title="Dashboard")
@HOME.route('/admin/dashboard')
@login_required
def admin_dashboard():
"""
Render the admin_dashboard template on the /admin/dashboard route
:return:
"""
# prevent non-admins from accessing the page
if not current_user.is_admin:
abort(403)
return render_template('home/admin_dashboard.html', title="Dashboard")
+3
View File
@@ -0,0 +1,3 @@
"""
Define routing rules for library related information
"""
+35
View File
@@ -0,0 +1,35 @@
"""
Define form to edit publisher, artists and books
"""
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, SelectField, IntegerField
from wtforms.validators import DataRequired, Optional, NumberRange
from bson import ObjectId
class PublisherForm(FlaskForm):
"""
Form to add and edit a Comic publisher
"""
name = StringField('Name', validators=[DataRequired()])
submit = SubmitField('Submit')
class AuthorForm(FlaskForm):
"""
Form to add and edit a Comic publisher
"""
name = StringField('Name', validators=[DataRequired()])
submit = SubmitField('Submit')
class BookForm(FlaskForm):
"""
Form to add and edit Comics
"""
title = StringField('Title', validators=[DataRequired()])
author = SelectField('Author', coerce=ObjectId)
publisher = SelectField('Publisher', coerce=ObjectId)
isbn = StringField('ISBN', validators=[Optional()])
year = IntegerField('Year', validators=[NumberRange(min=1970, max=2050)])
submit = SubmitField('Submit')
+55
View File
@@ -0,0 +1,55 @@
"""
This module declares the model for the library related information.
"""
from pymongo.write_concern import WriteConcern
from pymodm import MongoModel, fields
class Publisher(MongoModel):
"""
Class Publisher represents a publisher of a book.
"""
name = fields.CharField()
def __str__(self):
return "Publisher({})".format(self.name)
class Meta:
"""Sets the connection and connections details."""
connection_alias = 'kontor'
write_concern = WriteConcern(j=True)
class Author(MongoModel):
"""
Class Author represents an author of a book.
"""
name = fields.CharField()
def __str__(self):
return "Author({})".format(self.name)
class Meta:
"""Sets the connection and connections details."""
connection_alias = 'kontor'
write_concern = WriteConcern(j=True)
class Book(MongoModel):
"""
Class book represents a book.
"""
title = fields.CharField()
author = fields.ReferenceField(Author)
publisher = fields.ReferenceField(Publisher)
isbn = fields.CharField(blank=True)
year = fields.IntegerField(blank=True)
edition = fields.CharField()
def __str__(self):
return "Book({})".format(self.title)
class Meta:
"""Sets the connection and connections details."""
connection_alias = 'kontor'
write_concern = WriteConcern(j=True)
+228
View File
@@ -0,0 +1,228 @@
"""Define routing rules for book publishers"""
from flask import Blueprint, flash, redirect, render_template, url_for
from flask_login import login_required
from bson import ObjectId
from pymongo.errors import PyMongoError
from .forms import PublisherForm, AuthorForm, BookForm
from .models import Publisher, Author, Book
LIBRARY = Blueprint('library', __name__)
@LIBRARY.route('/publishers')
@login_required
def list_publishers():
"""
List all publishers
:return:
"""
publishers = Publisher.objects.all()
return render_template('library/publishers.html',
publishers=publishers, title="Publishers")
@LIBRARY.route('/publishers/add', methods=['GET', 'POST'])
@login_required
def add_publisher():
"""
Add a publisher to the database
:return:
"""
form = PublisherForm()
if form.validate_on_submit():
publisher = Publisher()
publisher.name = form.name.data
try:
publisher.save()
flash('You have successfully added a new publisher.')
except PyMongoError:
flash('Error: publisher name already exists.')
return redirect(url_for('library.list_publishers'))
return render_template('simpleform.html', action="Add",
form=form, title="Add Publisher")
@LIBRARY.route('/publishers/edit/<publisher_id>', methods=['GET', 'POST'])
@login_required
def edit_publisher(publisher_id):
"""
Edit a publisher
"""
publisher = Publisher.objects.get({'_id': ObjectId(publisher_id)})
form = PublisherForm(obj=publisher)
if form.validate_on_submit():
publisher.name = form.name.data
publisher.save()
flash('You have successfully edited the publisher.')
return redirect(url_for('library.list_publishers'))
form.name.data = publisher.name
return render_template('simpleform.html', action="Edit",
form=form, publisher=publisher, title="Edit Publisher")
@LIBRARY.route('/publishers/delete/<publisher_id>', methods=['GET', 'POST'])
@login_required
def delete_publisher(publisher_id):
"""
Delete a publisher
:param publisher_id: ObjectId of publisher
:return:
"""
publisher = Publisher.objects.raw({'_id': ObjectId(publisher_id)})
if publisher:
publisher.delete()
flash('You have successfully deleted the publisher.')
return redirect(url_for('library.list_publishers'))
@LIBRARY.route('/authors')
@login_required
def list_authors():
"""
List all authors
:return:
"""
authors = Author.objects.all()
return render_template('library/authors.html',
authors=authors, title="Authors")
@LIBRARY.route('/authors/edit/<author_id>', methods=['GET', 'POST'])
@login_required
def edit_author(author_id):
"""
Edit a book artist
"""
author = Author.objects.get({'_id': ObjectId(author_id)})
form = AuthorForm(obj=author)
if form.validate_on_submit():
author.name = form.name.data
author.save()
flash('You have successfully edited the author.')
return redirect(url_for('library.list_authors'))
form.name.data = author.name
return render_template('simpleform.html', action="Edit",
form=form, author=author, title="Edit Author")
@LIBRARY.route('/authors/add', methods=['GET', 'POST'])
@login_required
def add_author():
"""
Add a author
:return:
"""
form = AuthorForm()
if form.validate_on_submit():
author = Author()
author.name = form.name.data
try:
author.save()
flash('You have successfully added a new author.')
except PyMongoError:
flash('Error: author name already exists.')
return redirect(url_for('library.list_authors'))
return render_template('simpleform.html', action="Add",
form=form, title="Add Author")
@LIBRARY.route('/authors/delete/<author_id>', methods=['GET', 'POST'])
@login_required
def delete_author(author_id):
"""
Delete a author
:param author_id:
:return:
"""
author = Author.objects.raw({'_id': ObjectId(author_id)})
if author:
author.delete()
flash('You have successfully deleted the author.')
return redirect(url_for('library.list_authors'))
@LIBRARY.route('/books')
@login_required
def list_books():
"""
List all comics
:return:
"""
books = Book.objects.all()
return render_template('library/books.html',
books=books, title="Books")
@LIBRARY.route('/books/edit/<book_id>', methods=['GET', 'POST'])
@login_required
def edit_book(book_id):
"""
Edit a book
"""
book = Book.objects.get({'_id': ObjectId(book_id)})
form = BookForm(obj=book)
form.publisher.choices = [(p.pk, p.name) for p in Publisher.objects.all()]
if book.publisher:
form.publisher.default = book.publisher.pk
form.publisher.process_data(book.publisher.pk)
form.author.choices = [(a.pk, a.name) for a in Author.objects.all()]
if book.author:
form.author.default = book.author.pk
form.author.process_data(book.author.pk)
if form.validate_on_submit():
book.title = form.title.data
book.publisher = form.publisher.data
book.author = form.author.data
if form.isbn.data:
book.isbn = form.isbn.data
else:
book.isbn = None
book.year = form.year.data
book.save()
flash('You have successfully edited the book.')
return redirect(url_for('library.list_books'))
form.title.data = book.title
return render_template('simpleform.html', action="Edit",
form=form, book=book, title="Edit Book")
@LIBRARY.route('/books/add', methods=['GET', 'POST'])
@login_required
def add_book():
"""
Add a book
:return:
"""
form = BookForm()
form.publisher.choices = [(p.pk, p.name) for p in Publisher.objects.all()]
form.author.choices = [(a.pk, a.name) for a in Author.objects.all()]
if form.validate_on_submit():
book = Book()
book.title = form.title.data
book.publisher = form.publisher.data
book.author = form.author.data
book.year = form.year.data
try:
book.save()
flash('You have successfully added a new book.')
except PyMongoError:
flash('Error: book title already exists.')
return redirect(url_for('library.list_books'))
return render_template('simpleform.html', action="Add",
form=form, title="Add Book")
@LIBRARY.route('/books/delete/<book_id>', methods=['GET', 'POST'])
@login_required
def delete_book(book_id):
"""
Delete a book
:param book_id:
:return:
"""
book = Book.objects.raw({'_id': ObjectId(book_id)})
if book:
book.delete()
flash('You have successfully deleted the book.')
return redirect(url_for('library.list_books'))
+66
View File
@@ -0,0 +1,66 @@
"""
This modules declares the model for Kontor users.
"""
from pymodm import MongoModel, fields
from pymongo.write_concern import WriteConcern
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from kontor import _LOGIN_MANAGER_
class User(UserMixin, MongoModel):
"""
This class represents an user for the Kontor application.
"""
email = fields.EmailField()
username = fields.CharField(max_length=60)
first_name = fields.CharField(max_length=60)
last_name = fields.CharField(max_length=60)
password_hash = fields.CharField(max_length=128)
is_admin = fields.BooleanField(default=False)
@property
def password(self):
"""
Prevent pasword from being accessed
"""
raise AttributeError('password is not a readable attribute.')
@password.setter
def password(self, password):
"""
Set password to a hashed password
"""
self.password_hash = generate_password_hash(password)
def verify_password(self, password):
"""
Check if hashed password matches actual password
"""
return check_password_hash(self.password_hash, password)
def get_id(self):
"""
Get username as id
:return:
"""
return self.username
def __repr__(self):
return '<User: {}>'.format(self.username)
class Meta:
"""Sets the connection and connections details."""
connection_alias = 'kontor'
write_concern = WriteConcern(j=True)
# Set up user_loader
@_LOGIN_MANAGER_.user_loader
def load_user(user_name):
"""
Get list of users from database
:param user_name:
:return:
"""
return User.objects.get({'username': user_name})
+3
View File
@@ -0,0 +1,3 @@
"""
Define routing rules for office related information
"""
+126
View File
@@ -0,0 +1,126 @@
/* app/static/css/style.css */
body, html {
width: 100%;
height: 100%;
}
body, h1, h2, h3 {
font-family: "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: 700;
}
a, .navbar-default .navbar-brand, .navbar-default .navbar-nav>li>a {
color: #aec251;
}
a:hover, .navbar-default .navbar-brand:hover, .navbar-default .navbar-nav>li>a:hover {
color: #687430;
}
footer {
padding: 50px 0;
background-color: #f8f8f8;
}
p.copyright {
margin: 15px 0 0;
}
.alert-info {
width: 50%;
margin: auto;
color: #687430;
background-color: #e6ecca;
border-color: #aec251;
}
.btn-default {
border-color: #aec251;
color: #aec251;
}
.btn-default:hover {
background-color: #aec251;
}
.center {
margin: auto;
width: 70%;
padding: 10px;
}
.content-section {
padding: 50px 0;
border-top: 1px solid #e7e7e7;
}
.footer, .push {
clear: both;
height: 4em;
}
.intro-divider {
width: 400px;
border-top: 1px solid #f8f8f8;
border-bottom: 1px solid rgba(0,0,0,0.2);
}
.intro-header {
padding-top: 50px;
padding-bottom: 50px;
text-align: center;
color: #f8f8f8;
background: url(../img/intro-bg.jpg) no-repeat center center;
background-size: cover;
height: 100%;
}
.intro-message {
position: relative;
padding-top: 20%;
padding-bottom: 20%;
}
.intro-message > h1 {
margin: 0;
text-shadow: 2px 2px 3px rgba(0,0,0,0.6);
font-size: 5em;
}
.intro-message > h3 {
text-shadow: 2px 2px 3px rgba(0,0,0,0.6);
}
.lead {
font-size: 18px;
font-weight: 400;
}
.topnav {
font-size: 14px;
}
.wrapper {
min-height: 100%;
height: auto !important;
height: 100%;
margin: 0 auto -4em;
}
.outer {
display: table;
position: absolute;
height: 70%;
width: 100%;
}
.middle {
display: table-cell;
vertical-align: middle;
}
.inner {
margin-left: auto;
margin-right: auto;
}
@@ -0,0 +1,21 @@
<!-- app/templates/admin/employees/employee.html -->
{% import "bootstrap/wtf.html" as wtf %}
{% extends "base.html" %}
{% block title %}Edit User{% endblock %}
{% block body %}
<div class="content-section">
<div class="outer">
<div class="middle">
<div class="inner">
<div class="center">
<h1> Edit User </h1>
<br/>
<br/>
{{ wtf.quick_form(form) }}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
@@ -0,0 +1,58 @@
<!-- app/templates/admin/users/users.html -->
{% import "bootstrap/utils.html" as utils %}
{% extends "base.html" %}
{% block title %}Users{% endblock %}
{% block body %}
<div class="content-section">
<div class="outer">
<div class="middle">
<div class="inner">
<br/>
{{ utils.flashed_messages() }}
<br/>
<h1 style="text-align:center;">Users</h1>
{% if users %}
<hr class="intro-divider">
<div class="center">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th width="15%"> Name </th>
<th width="30%"> Email </th>
<th width="30%"> Username </th>
<th width="15%"> Edit </th>
</tr>
</thead>
<tbody>
{% for user in users %}
{% if user.is_admin %}
<tr style="background-color: #aec251; color: white;">
<td> <i class="fa fa-key"></i> Admin </td>
<td> N/A </td>
<td> N/A </td>
<td> N/A </td>
</tr>
{% else %}
<tr>
<td> {{ user.first_name }} {{ user.last_name }} </td>
<td> {{ user.email }} </td>
<td> {{ user.username }} </td>
<td>
<a href="{{ url_for('admin.edit_user', user_id=user.username) }}">
<i class="fa fa-user-plus"></i> Edit
</a>
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
+18
View File
@@ -0,0 +1,18 @@
<!-- app/templates/auth/login.html -->
{% import "bootstrap/utils.html" as utils %}
{% import "bootstrap/wtf.html" as wtf %}
{% extends "base.html" %}
{% block title %}Login{% endblock %}
{% block body %}
<div class="content-section">
<br/>
{{ utils.flashed_messages() }}
<br/>
<div class="center">
<h1>Login to your account</h1>
<br/>
{{ wtf.quick_form(form) }}
</div>
</div>
{% endblock %}
+14
View File
@@ -0,0 +1,14 @@
<!-- app/templates/auth/register.html -->
{% import "bootstrap/wtf.html" as wtf %}
{% extends "base.html" %}
{% block title %}Register{% endblock %}
{% block body %}
<div class="content-section">
<div class="center">
<h1>Register for an account</h1>
<br/>
{{ wtf.quick_form(form) }}
</div>
</div>
{% endblock %}
+107
View File
@@ -0,0 +1,107 @@
<!-- app/templates/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>{{ title }} | Kontor</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
<link rel="shortcut icon" href="{{ url_for('static', filename='img/favicon.ico') }}">
</head>
<body>
<nav class="navbar navbar-default navbar-fixed-top topnav" role="navigation">
<div class="container topnav">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<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 topnav" href="{{ url_for('home.homepage') }}">Kontor</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
{% if current_user.is_authenticated %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button">Comics <span class="caret"></span> </a>
<ul class="dropdown-menu">
<li><a href="{{ url_for('comic.list_comics') }}">Comics</a></li>
<li><a href="{{ url_for('comic.list_artists') }}">Artists</a></li>
<li><a href="{{ url_for('comic.list_publishers') }}">Publishers</a></li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button">Library <span class="caret"></span> </a>
<ul class="dropdown-menu">
<li><a href="{{ url_for('library.list_books') }}">Books</a></li>
<li><a href="{{ url_for('library.list_authors') }}">Authors</a></li>
<li><a href="{{ url_for('library.list_publishers') }}">Publishers</a></li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button">HomeOffice <span class="caret"></span> </a>
<ul class="dropdown-menu">
<li><a href="{{ url_for('home.homepage') }}">HomeOffice</a></li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button">Trading Cards <span class="caret"></span> </a>
<ul class="dropdown-menu">
<li><a href="{{ url_for('home.homepage') }}">Trading Cards</a></li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button">TradeYourSportsCards <span class="caret"></span> </a>
<ul class="dropdown-menu">
<li><a href="{{ url_for('tysc.list_sports') }}">Sports</a></li>
<li><a href="{{ url_for('tysc.list_positions') }}">Positions</a></li>
<li><a href="{{ url_for('tysc.list_teams') }}">Teams</a></li>
<li><a href="{{ url_for('tysc.list_players') }}">Players</a></li>
<li><a href="{{ url_for('tysc.list_manufacturers') }}">Manufacturers</a></li>
<li><a href="{{ url_for('tysc.list_card_sets') }}">Series</a></li>
<li><a href="{{ url_for('tysc.list_parallel_sets') }}">Parallel Sets</a></li>
<li><a href="{{ url_for('tysc.list_insert_sets') }}">Inserts</a></li>
<li><a href="{{ url_for('tysc.list_cards') }}">Cards</a></li>
</ul>
</li>
{% if current_user.is_admin %}
<li><a href="{{ url_for('home.admin_dashboard') }}">Dashboard</a></li>
<li><a href="{{ url_for('admin.list_users') }}">Users</a></li>
{% else %}
<li><a href="{{ url_for('home.dashboard') }}">Dashboard</a></li>
{% endif %}
<li><a href="{{ url_for('auth.logout') }}">{{ current_user.username }}</a></li>
{% else %}
<li><a href="{{ url_for('home.homepage') }}">Home</a></li>
<li><a href="{{ url_for('auth.register') }}">Register</a></li>
<li><a href="{{ url_for('auth.login') }}">Login</a></li>
{% endif %}
</ul>
</div>
</div>
</nav>
<div class="wrapper">
{% block body %}
{% endblock %}
<div class="push"></div>
</div>
<footer>
<div class="container">
<div class="row">
<p class="col-lg-12">
<ul class="list-inline">
<li><a href="{{ url_for('home.homepage') }}">Home</a></li>
<li class="footer-menu-divider"></li>
<li><a href="{{ url_for('auth.register') }}">Register</a></li>
<li class="footer-menu-divider"></li>
<li><a href="{{ url_for('auth.login') }}">Login</a></li>
</ul>
<p class="copyright text-muted small">Version {{ version }}. Copyright © 2017. All Rights Reserved</p>
</div>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,58 @@
{% import "bootstrap/utils.html" as utils %}
{% extends "base.html" %}
{% block title %}Comic Artists{% endblock %}
{% block body %}
<div class="content-section">
<div class="outer">
<div class="middle">
<div class="inner">
<br/>
{{ utils.flashed_messages() }}
<br/>
<h1 style="text-align:center;">Comic Artists</h1>
{% if artists %}
<hr class="intro-divider">
<div class="center">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th width="15%"> Name </th>
<th width="15%"> Edit </th>
<th width="15%"> Delete </th>
</tr>
</thead>
<tbody>
{% for artist in artists %}
<tr>
<td> {{ artist.name }} </td>
<td>
<a href="{{ url_for('comic.edit_artist', artist_id=artist.pk) }}">
<i class="fa fa-pencil"></i> Edit
</a>
</td>
<td>
<a href="{{ url_for('comic.delete_artist', artist_id=artist.pk) }}">
<i class="fa fa-trash"></i> Delete
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div style="text-align: center">
{% else %}
<div style="text-align: center">
<h3> No artists have been added. </h3>
<hr class="intro-divider">
{% endif %}
<a href="{{ url_for('comic.add_artist') }}" class="btn btn-default btn-lg">
<i class="fa fa-plus"></i>
Add Artist
</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
+66
View File
@@ -0,0 +1,66 @@
{% import "bootstrap/utils.html" as utils %}
{% extends "base.html" %}
{% block title %}Comics{% endblock %}
{% block body %}
<div class="content-section">
<div class="outer">
<div class="middle">
<div class="inner">
<br/>
{{ utils.flashed_messages() }}
<br/>
<h1 style="text-align:center;">Comics</h1>
{% if comics %}
<hr class="intro-divider">
<div class="center">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th width="15%"> Title </th>
<th width="40%"> Publisher </th>
<th width="15%"> Current Order </th>
<th width="15%"> Edit </th>
<th width="15%"> Delete </th>
</tr>
</thead>
<tbody>
{% for comic in comics %}
<tr>
<td> {{ comic.title }} </td>
<td>
<a href="{{ url_for('comic.edit_publisher', publisher_id=comic.publisher.pk) }}">
{{ comic.publisher.name }}
</a>
</td>
<td> {{ comic.current_order }} </td>
<td>
<a href="{{ url_for('comic.edit_comic', comic_id=comic.pk) }}">
<i class="fa fa-pencil"></i> Edit
</a>
</td>
<td>
<a href="{{ url_for('comic.delete_comic', comic_id=comic.pk) }}">
<i class="fa fa-trash"></i> Delete
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div style="text-align: center">
{% else %}
<div style="text-align: center">
<h3> No comics have been added. </h3>
<hr class="intro-divider">
{% endif %}
<a href="{{ url_for('comic.add_comic') }}" class="btn btn-default btn-lg">
<i class="fa fa-plus"></i>
Add Comic
</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
@@ -0,0 +1,58 @@
{% import "bootstrap/utils.html" as utils %}
{% extends "base.html" %}
{% block title %}Comic Publishers{% endblock %}
{% block body %}
<div class="content-section">
<div class="outer">
<div class="middle">
<div class="inner">
<br/>
{{ utils.flashed_messages() }}
<br/>
<h1 style="text-align:center;">Comic Publishers</h1>
{% if publishers %}
<hr class="intro-divider">
<div class="center">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th width="15%"> Name </th>
<th width="15%"> Edit </th>
<th width="15%"> Delete </th>
</tr>
</thead>
<tbody>
{% for publisher in publishers %}
<tr>
<td> {{ publisher.name }} </td>
<td>
<a href="{{ url_for('comic.edit_publisher', publisher_id=publisher.pk) }}">
<i class="fa fa-pencil"></i> Edit
</a>
</td>
<td>
<a href="{{ url_for('comic.delete_publisher', publisher_id=publisher.pk) }}">
<i class="fa fa-trash"></i> Delete
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div style="text-align: center">
{% else %}
<div style="text-align: center">
<h3> No comics have been added. </h3>
<hr class="intro-divider">
{% endif %}
<a href="{{ url_for('comic.add_publisher') }}" class="btn btn-default btn-lg">
<i class="fa fa-plus"></i>
Add Publisher
</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
@@ -0,0 +1,31 @@
<!-- app/templates/home/admin_dashboard.html -->
{% extends "base.html" %}
{% block title %}Admin Dashboard{% endblock %}
{% block body %}
<div class="intro-header">
<div class="container">
<div class="row">
<div class="col-lg-12">
<div class="intro-message">
<h2>Admin Dashboard</h2>
<hr class="intro-divider">
<ul>
<li><a href="{{ url_for('comic.list_comics') }}">Comics</a></li>
<ul>
<li><a href="{{ url_for('comic.list_publishers') }}">Publishers</a> </li>
<li><a href="{{ url_for('comic.list_artists') }}">Artists</a> </li>
</ul>
<li><a href="{{ url_for('library.list_books') }}">Library</a></li>
<ul>
<li><a href="{{ url_for('library.list_publishers') }}">Publishers</a> </li>
<li><a href="{{ url_for('library.list_authors') }}">Authors</a> </li>
</ul>
<li><a href="{{ url_for('home.homepage') }}">Office</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
@@ -0,0 +1,20 @@
<!-- app/templates/home/dashboard.html -->
{% extends "base.html" %}
{% block title %}Dashboard{% endblock %}
{% block body %}
<div class="intro-header">
<div class="container">
<div class="row">
<div class="col-lg-12">
<div class="intro-message">
<h1>The Dashboard</h1>
<h3>We made it here!</h3>
<hr class="intro-divider">
</ul>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
+20
View File
@@ -0,0 +1,20 @@
<!-- app/templates/home/index.html -->
{% extends "base.html" %}
{% block title %}Home{% endblock %}
{% block body %}
<div class="intro-header">
<div class="container">
<div class="row">
<div class="col-lg-12">
<div class="intro-message">
<h1>Project Kontor</h1>
<h3>Store everything!</h3>
<hr class="intro-divider">
</ul>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
@@ -0,0 +1,58 @@
{% import "bootstrap/utils.html" as utils %}
{% extends "base.html" %}
{% block title %}Library Authors{% endblock %}
{% block body %}
<div class="content-section">
<div class="outer">
<div class="middle">
<div class="inner">
<br/>
{{ utils.flashed_messages() }}
<br/>
<h1 style="text-align:center;">Library Authors</h1>
{% if authors %}
<hr class="intro-divider">
<div class="center">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th width="15%"> Name </th>
<th width="15%"> Edit </th>
<th width="15%"> Delete </th>
</tr>
</thead>
<tbody>
{% for author in authors %}
<tr>
<td> {{ author.name }} </td>
<td>
<a href="{{ url_for('library.edit_author', author_id=author.pk) }}">
<i class="fa fa-pencil"></i> Edit
</a>
</td>
<td>
<a href="{{ url_for('library.delete_author', author_id=author.pk) }}">
<i class="fa fa-trash"></i> Delete
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div style="text-align: center">
{% else %}
<div style="text-align: center">
<h3> No artists have been added. </h3>
<hr class="intro-divider">
{% endif %}
<a href="{{ url_for('library.add_author') }}" class="btn btn-default btn-lg">
<i class="fa fa-plus"></i>
Add Author
</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
+74
View File
@@ -0,0 +1,74 @@
{% import "bootstrap/utils.html" as utils %}
{% extends "base.html" %}
{% block title %}Books{% endblock %}
{% block body %}
<div class="content-section">
<div class="outer">
<div class="middle">
<div class="inner">
<br/>
{{ utils.flashed_messages() }}
<br/>
<h1 style="text-align:center;">Books</h1>
{% if books %}
<hr class="intro-divider">
<div class="center">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th width="15%"> Title </th>
<th width="25%"> Author </th>
<th width="40%"> Publisher </th>
<th width="5%"> ISBN </th>
<th width="2%"> Year </th>
<th width="15%"> Edit </th>
<th width="15%"> Delete </th>
</tr>
</thead>
<tbody>
{% for book in books %}
<tr>
<td> {{ book.title }} </td>
<td>
<a href="{{ url_for('library.edit_author', author_id=book.author.pk) }}">
{{ book.author.name }}
</a>
</td>
<td>
<a href="{{ url_for('library.edit_publisher', publisher_id=book.publisher.pk) }}">
{{ book.publisher.name }}
</a>
</td>
<td> {{ book.isbn }} </td>
<td> {{ book.year }} </td>
<td>
<a href="{{ url_for('library.edit_book', book_id=book.pk) }}">
<i class="fa fa-pencil"></i> Edit
</a>
</td>
<td>
<a href="{{ url_for('library.delete_book', book_id=book.pk) }}">
<i class="fa fa-trash"></i> Delete
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div style="text-align: center">
{% else %}
<div style="text-align: center">
<h3> No books have been added. </h3>
<hr class="intro-divider">
{% endif %}
<a href="{{ url_for('library.add_book') }}" class="btn btn-default btn-lg">
<i class="fa fa-plus"></i>
Add Book
</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
@@ -0,0 +1,58 @@
{% import "bootstrap/utils.html" as utils %}
{% extends "base.html" %}
{% block title %}Book Publishers{% endblock %}
{% block body %}
<div class="content-section">
<div class="outer">
<div class="middle">
<div class="inner">
<br/>
{{ utils.flashed_messages() }}
<br/>
<h1 style="text-align:center;">Book Publishers</h1>
{% if publishers %}
<hr class="intro-divider">
<div class="center">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th width="15%"> Name </th>
<th width="15%"> Edit </th>
<th width="15%"> Delete </th>
</tr>
</thead>
<tbody>
{% for publisher in publishers %}
<tr>
<td> {{ publisher.name }} </td>
<td>
<a href="{{ url_for('library.edit_publisher', publisher_id=publisher.pk) }}">
<i class="fa fa-pencil"></i> Edit
</a>
</td>
<td>
<a href="{{ url_for('library.delete_publisher', publisher_id=publisher.pk) }}">
<i class="fa fa-trash"></i> Delete
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div style="text-align: center">
{% else %}
<div style="text-align: center">
<h3> No publishers have been added. </h3>
<hr class="intro-divider">
{% endif %}
<a href="{{ url_for('library.add_publisher') }}" class="btn btn-default btn-lg">
<i class="fa fa-plus"></i>
Add Publisher
</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
+21
View File
@@ -0,0 +1,21 @@
{% import "bootstrap/wtf.html" as wtf %}
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block body %}
<div class="content-section">
<div class="outer">
<div class="middle">
<div class="inner">
<div class="center">
<h1> {{ title }} </h1>
<br/>
<br/>
{{ wtf.quick_form(form) }}
<br/>
<hr class="intro-divider">
</div>
</div>
</div>
</div>
</div>
{% endblock %}
@@ -0,0 +1,58 @@
{% import "bootstrap/utils.html" as utils %}
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block body %}
<div class="content-section">
<div class="outer">
<div class="middle">
<div class="inner">
<br/>
{{ utils.flashed_messages() }}
<br/>
<h1 style="text-align:center;">{{ title }}</h1>
{% if manufacturers %}
<hr class="intro-divider">
<div class="center">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th width="15%"> Name </th>
<th width="15%"> Edit </th>
<th width="15%"> Delete </th>
</tr>
</thead>
<tbody>
{% for manufacturer in manufacturers %}
<tr>
<td> {{ manufacturer.name }} </td>
<td>
<a href="{{ url_for('tysc.edit_manufacturer', manufacturer_id=manufacturer.pk) }}">
<i class="fa fa-pencil"></i> Edit
</a>
</td>
<td>
<a href="{{ url_for('tysc.delete_manufacturer', manufacturer_id=manufacturer.pk) }}">
<i class="fa fa-trash"></i> Delete
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div style="text-align: center">
{% else %}
<div style="text-align: center">
<h3> No manufacturers have been added. </h3>
<hr class="intro-divider">
{% endif %}
<a href="{{ url_for('tysc.add_manufacturer') }}" class="btn btn-default btn-lg">
<i class="fa fa-plus"></i>
Add Manufacturer
</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
+60
View File
@@ -0,0 +1,60 @@
{% import "bootstrap/utils.html" as utils %}
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block body %}
<div class="content-section">
<div class="outer">
<div class="middle">
<div class="inner">
<br/>
{{ utils.flashed_messages() }}
<br/>
<h1 style="text-align:center;">{{ title }}</h1>
{% if players %}
<hr class="intro-divider">
<div class="center">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th width="15%"> First Name </th>
<th width="15%"> Last Name </th>
<th width="15%"> Edit </th>
<th width="15%"> Delete </th>
</tr>
</thead>
<tbody>
{% for player in players %}
<tr>
<td> {{ player.first_name }} </td>
<td> {{ player.last_name }} </td>
<td>
<a href="{{ url_for('tysc.edit_player', player_id=player.pk) }}">
<i class="fa fa-pencil"></i> Edit
</a>
</td>
<td>
<a href="{{ url_for('tysc.delete_player', player_id=player.pk) }}">
<i class="fa fa-trash"></i> Delete
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div style="text-align: center">
{% else %}
<div style="text-align: center">
<h3> No players have been added. </h3>
<hr class="intro-divider">
{% endif %}
<a href="{{ url_for('tysc.add_player') }}" class="btn btn-default btn-lg">
<i class="fa fa-plus"></i>
Add Player
</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
@@ -0,0 +1,62 @@
{% import "bootstrap/utils.html" as utils %}
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block body %}
<div class="content-section">
<div class="outer">
<div class="middle">
<div class="inner">
<br/>
{{ utils.flashed_messages() }}
<br/>
<h1 style="text-align:center;">{{ title }}</h1>
{% if positions %}
<hr class="intro-divider">
<div class="center">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th width="25%"> Name </th>
<th width="15%"> Description </th>
<th width="15%"> Sport </th>
<th width="15%"> Edit </th>
<th width="15%"> Delete </th>
</tr>
</thead>
<tbody>
{% for position in positions %}
<tr>
<td> {{ position.name }} </td>
<td> {{ position.description }} </td>
<td> {{ position.sport.name }} </td>
<td>
<a href="{{ url_for('tysc.edit_position', position_id=position.pk) }}">
<i class="fa fa-pencil"></i> Edit
</a>
</td>
<td>
<a href="{{ url_for('tysc.delete_position', position_id=position.pk) }}">
<i class="fa fa-trash"></i> Delete
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div style="text-align: center">
{% else %}
<div style="text-align: center">
<h3> No positions have been added. </h3>
<hr class="intro-divider">
{% endif %}
<a href="{{ url_for('tysc.add_position') }}" class="btn btn-default btn-lg">
<i class="fa fa-plus"></i>
Add Position
</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
+58
View File
@@ -0,0 +1,58 @@
{% import "bootstrap/utils.html" as utils %}
{% extends "base.html" %}
{% block title %}Sports{% endblock %}
{% block body %}
<div class="content-section">
<div class="outer">
<div class="middle">
<div class="inner">
<br/>
{{ utils.flashed_messages() }}
<br/>
<h1 style="text-align:center;">Sports</h1>
{% if sports %}
<hr class="intro-divider">
<div class="center">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th width="15%"> Name </th>
<th width="15%"> Edit </th>
<th width="15%"> Delete </th>
</tr>
</thead>
<tbody>
{% for sport in sports %}
<tr>
<td> {{ sport.name }} </td>
<td>
<a href="{{ url_for('tysc.edit_sport', sport_id=sport.pk) }}">
<i class="fa fa-pencil"></i> Edit
</a>
</td>
<td>
<a href="{{ url_for('tysc.delete_sport', sport_id=sport.pk) }}">
<i class="fa fa-trash"></i> Delete
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div style="text-align: center">
{% else %}
<div style="text-align: center">
<h3> No sports have been added. </h3>
<hr class="intro-divider">
{% endif %}
<a href="{{ url_for('tysc.add_sport') }}" class="btn btn-default btn-lg">
<i class="fa fa-plus"></i>
Add Sport
</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
+60
View File
@@ -0,0 +1,60 @@
{% import "bootstrap/utils.html" as utils %}
{% extends "base.html" %}
{% block title %}Teams{% endblock %}
{% block body %}
<div class="content-section">
<div class="outer">
<div class="middle">
<div class="inner">
<br/>
{{ utils.flashed_messages() }}
<br/>
<h1 style="text-align:center;">Teams</h1>
{% if teams %}
<hr class="intro-divider">
<div class="center">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th width="25%"> Name </th>
<th width="15%"> Shortname </th>
<th width="15%"> Edit </th>
<th width="15%"> Delete </th>
</tr>
</thead>
<tbody>
{% for team in teams %}
<tr>
<td> {{ team.name }} </td>
<td> {{ team.shortname }} </td>
<td>
<a href="{{ url_for('tysc.edit_team', team_id=team.pk) }}">
<i class="fa fa-pencil"></i> Edit
</a>
</td>
<td>
<a href="{{ url_for('tysc.delete_team', team_id=team.pk) }}">
<i class="fa fa-trash"></i> Delete
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div style="text-align: center">
{% else %}
<div style="text-align: center">
<h3> No teams have been added. </h3>
<hr class="intro-divider">
{% endif %}
<a href="{{ url_for('tysc.add_team') }}" class="btn btn-default btn-lg">
<i class="fa fa-plus"></i>
Add Team
</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
+377
View File
@@ -0,0 +1,377 @@
"""
Define routing rules for TradeYourSportsCards related information
"""
from .models import Sport, Team, Position, Player
from .models import Manufacturer, CardSet, ParallelSet, InsertSet, Card
def initialize_model():
"""
Initialize collections for module TradeYourSportsCards.
"""
initialize_sport()
initialize_position()
initialize_teams()
initialize_players()
initialize_manufacturers()
initialize_card_sets()
initialize_parallel_sets()
initialize_insert_sets()
initialize_cards()
def initialize_sport():
"""
Initialize collection Sport with american sports.
"""
for sport in Sport.objects.all():
sport.delete()
sports = ["Baseball", "Basketball", "Football", "Hockey"]
for sport_name in sports:
sport = Sport()
sport.name = sport_name
sport.save()
def initialize_position():
"""
Initialize collection Position with data.
"""
for position in Position.objects.all():
position.delete()
positions = {
'Quarterback': {'short': 'QB', 'sport': 'Football'},
'Wide Receiver': {'short': 'WR', 'sport': 'Football'},
'Runningback': {'short': 'RB', 'sport': 'Football'},
'Linebacker': {'short': 'LB', 'sport': 'Football'},
'Tight End': {'short': 'TE', 'sport': 'Football'},
'Fullback': {'short': 'FB', 'sport': 'Football'},
'Strong Safety': {'short': 'SS', 'sport': 'Football'},
'Defensive End': {'short': 'DE', 'sport': 'Football'},
'Kicker': {'short': 'K', 'sport': 'Football'},
'Punter': {'short': 'P', 'sport': 'Football'},
'Left Guard': {'short': 'LG', 'sport': 'Football'},
'Right Guard': {'short': 'RG', 'sport': 'Football'},
'Offensive Tackle': {'short': 'OT', 'sport': 'Football'},
'Defensive Back': {'short': 'DB', 'sport': 'Football'},
'Cornerback': {'short': 'CB', 'sport': 'Football'},
'Catcher': {'short': 'C', 'sport': 'Baseball'},
'First Base': {'short': '1B', 'sport': 'Baseball'},
'Second Base': {'short': '2B', 'sport': 'Baseball'},
'Third Base': {'short': '3B', 'sport': 'Baseball'},
'Shortstop': {'short': 'SS', 'sport': 'Baseball'},
'Left Field': {'short': 'LF', 'sport': 'Baseball'},
'Center Field': {'short': 'CF', 'sport': 'Baseball'},
'Right Field': {'short': 'RF', 'sport': 'Baseball'},
'Designated Hitter': {'short': 'DH', 'sport': 'Baseball'},
'Pitcher': {'short': 'P', 'sport': 'Baseball'}
}
for key in positions:
sport = Sport.objects.get({'name': positions.get(key)['sport']})
position = Position()
position.description = key
position.name = positions.get(key)['short']
position.sport = sport
position.save()
def initialize_teams():
"""
Initialize collection Team with data.
"""
for team in Team.objects.all():
team.delete()
teams = {'Buffalo Bills': {'short': 'Bills', 'sport': 'Football'},
'Indianapolis Colts': {'short': 'Colts', 'sport': 'Football'},
'Miami Dolphins': {'short': 'Dolphins', 'sport': 'Football'},
'New England Patriots': {'short': 'Patriots', 'sport': 'Football'},
'New York Jets': {'short': 'Jets', 'sport': 'Football'},
'Baltimore Ravens': {'short': 'Ravens', 'sport': 'Football'},
'Cincinnati Bengals': {'short': 'Bengals', 'sport': 'Football'},
'Cleveland Browns': {'short': 'Browns', 'sport': 'Football'},
'Jacksonville Jaguars': {'short': 'Jaguars', 'sport': 'Football'},
'Pittsburgh Steelers': {'short': 'Steelers', 'sport': 'Football'},
'Tennessee Titans': {'short': 'Titans', 'sport': 'Football'},
'Denver Broncos': {'short': 'Broncos', 'sport': 'Football'},
'Kansas City Chiefs': {'short': 'Chiefs', 'sport': 'Football'},
'Oakland Raiders': {'short': 'Raiders', 'sport': 'Football'},
'San Diego Chargers': {'short': 'Chargers', 'sport': 'Football'},
'Seattle Seahawks': {'short': 'Seahawks', 'sport': 'Football'},
'Arizona Cardinals': {'short': 'Cardinals', 'sport': 'Football'},
'Dallas Cowboys': {'short': 'Cowboys', 'sport': 'Football'},
'New York Giants': {'short': 'Giants', 'sport': 'Football'},
'Philadelphia Eagles': {'short': 'Eagles', 'sport': 'Football'},
'Washington Redskins': {'short': 'Redskins', 'sport': 'Football'},
'Chicago Bears': {'short': 'Bears', 'sport': 'Football'},
'Detroit Lions': {'short': 'Lions', 'sport': 'Football'},
'Green Bay Packers': {'short': 'Packers', 'sport': 'Football'},
'Minnesota Vikings': {'short': 'Vikings', 'sport': 'Football'},
'Tampa Bay Buccaneers': {'short': 'Buccaneers', 'sport': 'Football'},
'Atlanta Falcons': {'short': 'Falcons', 'sport': 'Football'},
'Carolina Panthers': {'short': 'Panthers', 'sport': 'Football'},
'New Orleans Saints': {'short': 'Saints', 'sport': 'Football'},
'St.Louis Rams': {'short': 'Rams', 'sport': 'Football'},
'San Francisco 49ers': {'short': '49ers', 'sport': 'Football'},
'Baltimore Orioles': {'short': 'Orioles', 'sport': 'Baseball'},
'Boston Red Sox': {'short': 'Red Sox', 'sport': 'Baseball'},
'New York Yankees': {'short': 'Yankees', 'sport': 'Baseball'},
'Tampa Bay Devil Rays': {'short': 'Devil Rays', 'sport': 'Baseball'},
'Toronto Blue Jays': {'short': 'Blue Jays', 'sport': 'Baseball'},
'Chicago White Sox': {'short': 'White Sox', 'sport': 'Baseball'},
'Cleveland Indians': {'short': 'Indians', 'sport': 'Baseball'},
'Detroit Tigers': {'short': 'Tigers', 'sport': 'Baseball'},
'Kansas City Royals': {'short': 'Royals', 'sport': 'Baseball'},
'Minnesota Twins': {'short': 'Twins', 'sport': 'Baseball'},
'Anaheim Angels': {'short': 'Angels', 'sport': 'Baseball'},
'Oakland Athletics': {'short': 'Athletics', 'sport': 'Baseball'},
'Seattle Mariners': {'short': 'Mariners', 'sport': 'Baseball'},
'Texas Rangers': {'short': 'Rangers', 'sport': 'Baseball'},
'Atlanta Braves': {'short': 'Braves', 'sport': 'Baseball'},
'Florida Marlins': {'short': 'Marlins', 'sport': 'Baseball'},
'Montreal Expos': {'short': 'Expos', 'sport': 'Baseball'},
'New York Mets': {'short': 'Mets', 'sport': 'Baseball'},
'Philadelphia Phillies': {'short': 'Phillies', 'sport': 'Baseball'},
'Chicago Cubs': {'short': 'Cubs', 'sport': 'Baseball'},
'Cincinnati Reds': {'short': 'Reds', 'sport': 'Baseball'},
'Houston Astros': {'short': 'Astros', 'sport': 'Baseball'},
'Milwaukee Brewers': {'short': 'Brewers', 'sport': 'Baseball'},
'Pittsburgh Pirates': {'short': 'Pirates', 'sport': 'Baseball'},
'St.Louis Cardinals': {'short': 'Cardinals', 'sport': 'Baseball'},
'Arizona Diamondbacks': {'short': 'Diamondbacks', 'sport': 'Baseball'},
'Colorado Rockies': {'short': 'Rockies', 'sport': 'Baseball'},
'Los Angeles Dodgers': {'short': 'Dodgers', 'sport': 'Baseball'},
'San Diego Padres': {'short': 'Padres', 'sport': 'Baseball'},
'San Francisco Giants': {'short': 'Giants', 'sport': 'Baseball'},
'Boston Celtics': {'short': 'Celtics', 'sport': 'Basketball'},
'Miami Heat': {'short': 'Heat', 'sport': 'Basketball'},
'New Jersey Nets': {'short': 'Mets', 'sport': 'Basketball'},
'New York Knicks': {'short': 'Knicks', 'sport': 'Basketball'},
'Orlando Magic': {'short': 'Magic', 'sport': 'Basketball'},
'Philadelphia 76ers': {'short': '76ers', 'sport': 'Basketball'},
'Washington Wizards': {'short': 'Wizards', 'sport': 'Basketball'},
'Atlanta Hawks': {'short': 'Hawks', 'sport': 'Basketball'},
'Charlotte Hornets': {'short': 'Hornets', 'sport': 'Basketball'},
'Chicago Bulls': {'short': 'Bulls', 'sport': 'Basketball'},
'Cleveland Cavaliers': {'short': 'Cavaliers', 'sport': 'Basketball'},
'Detroit Pistons': {'short': 'Pistons', 'sport': 'Basketball'},
'Indiana Pacers': {'short': 'Pacers', 'sport': 'Basketball'},
'Milwaukee Bucks': {'short': 'Bucks', 'sport': 'Basketball'},
'Toronto Raptors': {'short': 'Raptors', 'sport': 'Basketball'},
'Dallas Mavericks': {'short': 'Mavericks', 'sport': 'Basketball'},
'Denver Nuggets': {'short': 'Nuggets', 'sport': 'Basketball'},
'Houston Rockets': {'short': 'Rockets', 'sport': 'Basketball'},
'Minnesota Timberwolves': {'short': 'Timberwolves', 'sport': 'Basketball'},
'San Antonio Spurs': {'short': 'Spurs', 'sport': 'Basketball'},
'Utah Jazz': {'short': 'Jazz', 'sport': 'Basketball'},
'Vancouver Grizzlies': {'short': 'Grizzlies', 'sport': 'Basketball'},
'Golden State Warriors': {'short': 'Warriors', 'sport': 'Basketball'},
'Los Angeles Clippers': {'short': 'Clippers', 'sport': 'Basketball'},
'Los Angeles Lakers': {'short': 'Lakers', 'sport': 'Basketball'},
'Phoenix Suns': {'short': 'Suns', 'sport': 'Basketball'},
'Portland Trail Blazers': {'short': 'Blazers', 'sport': 'Basketball'},
'Sacramento Kings': {'short': 'Kings', 'sport': 'Basketball'},
'Seattle SuperSonics': {'short': 'SuperSonics', 'sport': 'Basketball'},
'Boston Bruins': {'short': 'Bruins', 'sport': 'Hockey'},
'Buffalo Sabres': {'short': 'Sabres', 'sport': 'Hockey'},
'Montreal Canadiens': {'short': 'Canadiens', 'sport': 'Hockey'},
'Ottawa Senators': {'short': 'Senators', 'sport': 'Hockey'},
'Toronto Maple Leafs': {'short': 'Maple Leafs', 'sport': 'Hockey'},
'New Jersey Devils': {'short': 'Devils', 'sport': 'Hockey'},
'New York Islander': {'short': 'Islander', 'sport': 'Hockey'},
'New York Rangers': {'short': 'Rangers', 'sport': 'Hockey'},
'Philadelphia Flyers': {'short': 'Flyers', 'sport': 'Hockey'},
'Pittsburgh Penguins': {'short': 'Penguins', 'sport': 'Hockey'},
'Atlanta Trashers': {'short': 'Trashers', 'sport': 'Hockey'},
'Carolina Hurricanes': {'short': 'Hurricanes', 'sport': 'Hockey'},
'Florida Panthers': {'short': 'Panthers', 'sport': 'Hockey'},
'Tampa Bay Lightnings': {'short': 'Lightnings', 'sport': 'Hockey'},
'Washington Capitals': {'short': 'Capitals', 'sport': 'Hockey'},
'Chicago Blackhawks': {'short': 'Blackhawks', 'sport': 'Hockey'},
'Columbo Blue Jackets': {'short': 'Blue Jackets', 'sport': 'Hockey'},
'Detroit Red Wings': {'short': 'Red Wings', 'sport': 'Hockey'},
'Nashville Predators': {'short': 'Predators', 'sport': 'Hockey'},
'St.Louis Blues': {'short': 'Blues', 'sport': 'Hockey'},
'Calgary Flames': {'short': 'Flames', 'sport': 'Hockey'},
'Colorado Avalanche': {'short': 'Avalanche', 'sport': 'Hockey'},
'Edmonton Oilers': {'short': 'Oilers', 'sport': 'Hockey'},
'Minnesota Wild': {'short': 'Wild', 'sport': 'Hockey'},
'Vancouver Canucks': {'short': 'Canucks', 'sport': 'Hockey'},
'Anaheim Mighty Ducks': {'short': 'Mighty Ducks', 'sport': 'Hockey'},
'Dallas Stars': {'short': 'Stars', 'sport': 'Hockey'},
'Los Angeles Kings': {'short': 'Kings', 'sport': 'Hockey'},
'Phoenix Coyotes': {'short': 'Coyotes', 'sport': 'Hockey'},
'San Jose Sharks': {'short': 'Sharks', 'sport': 'Hockey'},
'Houston Texans': {'short': 'Texans', 'sport': 'Football'},
'Houston Oilers': {'short': 'Oilers', 'sport': 'Football'}
}
for key in teams:
sport = Sport.objects.get({'name': teams.get(key)['sport']})
team = Team()
team.name = key
team.shortname = teams.get(key)['short']
team.sport = sport
team.save()
def initialize_players():
"""
Initialize collection Manufacturer with data.
"""
for player in Player.objects.all():
player.delete()
players = ['Pathon, Jerome', 'Bruschi, Tedy', 'Couch, Tim', 'Shea, Aaron',
'Lewis, Jamal', 'Lewis, Jermaine', 'Banks, Tony', 'Fuamatu-Ma\'Afala, Chris',
'Bettis, Jerome', 'Stewart, Kordell', 'Moon, Warren', 'Lockett, Kevin',
'Gannon, Rich', 'Jett, James', 'Strong, Mack', 'Huard, Brock',
'Watters, Ricky', 'Aikman, Troy', 'LaFleur, David', 'Brazzell, Chris',
'Dayne, Ron', 'Brown, Na', 'Small, Torrance', 'Lewis, Chad', 'Murrell, Adrian',
'Smith, Maurice', 'Chandler, Chris', 'Kanell, Danny', 'Williams, Ricky',
'Garcia, Jeff', 'Streets, Tai', 'Garner, Charlie', 'Rice, Jerry',
'Owens, Terrell', 'Bruce, Isaac', 'Canidate, Trung']
for player_name in players:
player = Player()
(last_name, first_name) = player_name.split(',')
player.last_name = last_name.strip()
player.first_name = first_name.strip()
player.save()
def initialize_manufacturers():
"""
Initialize collection Manufacturer with data.
"""
for manufacturer in Manufacturer.objects.all():
manufacturer.delete()
manufacturers = ['Pacific', 'Fleer', 'Bowman', 'Topps', 'Donruss',
'Score', 'Flair', 'Upper Deck']
for manufacturer_name in manufacturers:
manufacturer = Manufacturer()
manufacturer.name = manufacturer_name
manufacturer.save()
def initialize_card_sets():
"""
Initialize collection CardSet with data.
"""
for card_set in CardSet.objects.all():
card_set.delete()
card_sets = {'Pacific': 'Pacific',
'Fleer': 'Fleer',
'Bowman': 'Bowman',
'Leaf': 'Topps',
'Ultra': 'Fleer',
'Mystique': 'Fleer',
'Finest Hour': 'Pacific',
'SP': 'Upper Deck',
'SPx': 'Upper Deck',
'SP Authentic': 'Upper Deck',
'Black Diamond': 'Upper Deck'
}
for set_name in card_sets:
card_set = CardSet()
card_set.name = set_name
card_set.manufacturer = Manufacturer.objects.get({'name': card_sets.get(set_name)})
card_set.save()
def initialize_parallel_sets():
"""
Initialize collection ParallelSet with data.
"""
for parallel_set in ParallelSet.objects.all():
parallel_set.delete()
parallel_sets = {'Mystique Gold': 'Fleer',
'Pacific Copper': 'Pacific',
'Pacific Gold': 'Pacific'
}
for key in parallel_sets:
manufacturer = Manufacturer.objects.get({'name': parallel_sets.get(key)})
parallel_set = ParallelSet()
parallel_set.name = key
parallel_set.manufacturer = manufacturer
parallel_set.save()
def initialize_insert_sets():
"""
Initialize collection InsertSet with data.
"""
for insert_set in InsertSet.objects.all():
insert_set.delete()
manufacturer = Manufacturer.objects.get({'name': 'Fleer'})
insert_set = InsertSet()
insert_set.name = 'Mystique Big Buzz'
insert_set.manufacturer = manufacturer
insert_set.save()
def initialize_cards():
"""
Initialize collection Card with data.
"""
for card in Card.objects.all():
card.delete()
players = ['Pathon, Jerome', 'Bruschi, Tedy', 'Couch, Tim', 'Shea, Aaron',
'Lewis, Jamal', 'Lewis, Jermaine', 'Banks, Tony', 'Fuamatu-Ma\'Afala, Chris',
'Bettis, Jerome', 'Stewart, Kordell', 'Moon, Warren', 'Lockett, Kevin',
'Gannon, Rich', 'Jett, James', 'Strong, Mack', 'Huard, Brock',
'Watters, Ricky', 'Aikman, Troy', 'LaFleur, David', 'Brazzell, Chris',
'Dayne, Ron', 'Brown, Na', 'Small, Torrance', 'Lewis, Chad', 'Murrell, Adrian',
'Smith, Maurice', 'Chandler, Chris', 'Kanell, Danny', 'Williams, Ricky',
'Garcia, Jeff', 'Streets, Tai', 'Garner, Charlie', 'Rice, Jerry',
'Owens, Terrell', 'Bruce, Isaac', 'Canidate, Trung']
cards = [
[0, 'Indianapolis Colts', 'Pacific', 'Pacific', None, None, False, 2001, 185],
[1, 'Indianapolis Colts', 'Pacific', 'Pacific', None, None, False, 2001, 250],
[2, 'Cleveland Browns', 'Pacific', 'Pacific', None, None, False, 2001, 103],
[3, 'Cleveland Browns', 'Pacific', 'Pacific', None, None, False, 2001, 112],
[4, 'Baltimore Ravens', 'Pacific', 'Pacific', None, None, False, 2001, 37],
[5, 'Baltimore Ravens', 'Pacific', 'Pacific', None, None, False, 2001, 38],
[6, 'Baltimore Ravens', 'Pacific', 'Pacific', None, None, False, 2001, 31],
[7, 'Pittsburgh Steelers', 'Pacific', 'Pacific', None, None, False, 2001, 338],
[8, 'Pittsburgh Steelers', 'Pacific', 'Pacific', None, None, False, 2001, 335],
[9, 'Pittsburgh Steelers', 'Pacific', 'Pacific', None, None, False, 2001, 345],
[10, 'Kansas City Chiefs', 'Pacific', 'Pacific', None, None, False, 2001, 213],
[11, 'Kansas City Chiefs', 'Pacific', 'Pacific', None, None, False, 2001, 212],
[12, 'Oakland Raiders', 'Pacific', 'Pacific', None, None, False, 2001, 311],
[13, 'Oakland Raiders', 'Pacific', 'Pacific', None, None, False, 2001, 312],
[14, 'Seattle Seahawks', 'Pacific', 'Pacific', None, None, False, 2001, 403],
[15, 'Seattle Seahawks', 'Pacific', 'Pacific', None, None, False, 2001, 397],
[16, 'Seattle Seahawks', 'Pacific', 'Pacific', None, None, False, 2001, 404],
[17, 'Dallas Cowboys', 'Pacific', 'Pacific', None, None, False, 2001, 116],
[18, 'Dallas Cowboys', 'Pacific', 'Pacific', None, None, False, 2001, 122],
[19, 'Dallas Cowboys', 'Pacific', 'Pacific', None, None, False, 2001, 117],
[20, 'New York Giants', 'Pacific', 'Pacific', None, None, False, 2001, 281],
[21, 'New York Giants', 'Pacific', 'Pacific', None, None, False, 2001, 321],
[22, 'Philadelphia Eagles', 'Pacific', 'Pacific', None, None, False, 2001, 331],
[23, 'Philadelphia Eagles', 'Pacific', 'Pacific', None, None, False, 2001, 324],
[24, 'Washington Redskins', 'Pacific', 'Pacific', None, None, False, 2001, 445],
[25, 'Atlanta Falcons', 'Pacific', 'Pacific', None, None, False, 2001, 28],
[26, 'Atlanta Falcons', 'Pacific', 'Pacific', None, None, False, 2001, 17],
[27, 'Atlanta Falcons', 'Pacific', 'Pacific', None, None, False, 2001, 23],
[28, 'New Orleans Saints', 'Pacific', 'Pacific', None, None, False, 2001, 273],
[29, 'San Francisco 49ers', 'Pacific', 'Pacific', None, None, False, 2001, 380],
[30, 'San Francisco 49ers', 'Pacific', 'Pacific', None, None, False, 2001, 390],
[31, 'San Francisco 49ers', 'Pacific', 'Pacific', None, None, False, 2001, 381],
[32, 'San Francisco 49ers', 'Pacific', 'Pacific', None, None, False, 2001, 387],
[33, 'San Francisco 49ers', 'Pacific', 'Pacific', None, None, False, 2001, 386],
[34, 'St.Louis Rams', 'Pacific', 'Pacific', None, None, False, 2001, 349],
[35, 'St.Louis Rams', 'Pacific', 'Pacific', None, None, False, 2001, 350],
]
for card_data in cards:
card = Card()
player_name = players[card_data[0]]
(last_name, first_name) = player_name.split(',')
card.player = Player.objects.get(
{'last_name': last_name.strip(), 'first_name': first_name.strip()}
)
card.team = Team.objects.get({'name': card_data[1]})
card.manufacturer = Manufacturer.objects.get({'name': card_data[2]})
card.card_set = CardSet.objects.get({'name': card_data[3]})
card.parallel_set = card_data[4]
card.insert_set = card_data[5]
card.rookie = card_data[6]
card.year = card_data[7]
card.number = card_data[8]
card.save()
+76
View File
@@ -0,0 +1,76 @@
"""
Define form to edit sport, teams, player, manufacturers and card types
"""
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, SelectField, IntegerField, BooleanField
from wtforms.validators import DataRequired, Optional, NumberRange
from bson import ObjectId
class SportForm(FlaskForm):
"""
Form to add and edit a Sport
"""
name = StringField('Name', validators=[DataRequired()])
submit = SubmitField('Submit')
class TeamForm(FlaskForm):
"""
Form to add and edit a team
"""
name = StringField('Name', validators=[DataRequired()])
shortname = StringField('Shortname', validators=[DataRequired()])
sport = SelectField('Sport', coerce=ObjectId)
submit = SubmitField('Submit')
class PlayerForm(FlaskForm):
"""
Form to add and edit a player.
"""
first_name = StringField('First name', validators=[DataRequired()])
last_name = StringField('Last name', validators=[DataRequired()])
submit = SubmitField('Submit')
class PositionForm(FlaskForm):
"""
Form to add and edit field positions for sports
"""
name = StringField('Name', validators=[DataRequired()])
description = StringField('Description', validators=[DataRequired()])
sport = SelectField('Sport', coerce=ObjectId)
submit = SubmitField('Submit')
class ManufacturerForm(FlaskForm):
"""
Form to add and edit a card manufacturer.
"""
name = StringField('Name', validators=[DataRequired()])
submit = SubmitField('Submit')
class CardSetForm(FlaskForm):
"""
Form to add and edit a regular card set
"""
name = StringField('Name', validators=[DataRequired()])
manufacturer = SelectField('Manufacturer', coerce=ObjectId)
submit = SubmitField('Submit')
class CardForm(FlaskForm):
"""
Form to add and edit a trading card
"""
player = SelectField('Player', coerce=ObjectId)
team = SelectField('Team', coerce=ObjectId)
manufacturer = SelectField('Manufacturer', coerce=ObjectId)
card_set = SelectField('Card Set', coerce=ObjectId)
parallel_set = SelectField('Parallel Set', coerce=ObjectId, validators=[Optional()])
insert_set = SelectField('Inserts', coerce=ObjectId, validators=[Optional()])
rookie = BooleanField('Rookie', validators=[DataRequired()])
year = IntegerField('Year', validators=[NumberRange(min=1956, max=2020)])
number = IntegerField('Number', validators=[DataRequired()])
+155
View File
@@ -0,0 +1,155 @@
"""This modules declares the model for TradingCards related information."""
from pymongo.write_concern import WriteConcern
from pymodm import MongoModel, fields
class Sport(MongoModel):
"""Class Sport represents a sport."""
name = fields.CharField()
def __str__(self):
"""Returns printable version of Sport object."""
return self.name
def __repr__(self):
"""Returns printable version of Sport object."""
return "Sport({})".format(self.name)
class Meta:
"""Sets the connection and connections details."""
connection_alias = 'kontor'
write_concern = WriteConcern(j=True)
class Position(MongoModel):
"""Class Position represents the position of a player for a sport."""
name = fields.CharField(max_length=4)
description = fields.CharField(max_length=30)
sport = fields.ReferenceField(Sport)
def __str__(self):
"""Returns printable version of Position object."""
return "{0}({1})".format(self.name, self.description)
def __repr__(self):
"""Returns printable version of Position object."""
return "Position({0}, {1})".format(self.name, self.description)
class Meta:
"""Sets the connection and connections details."""
connection_alias = 'kontor'
write_concern = WriteConcern(j=True)
class Team(MongoModel):
"""Class Team represents a team for a sport."""
name = fields.CharField(max_length=60)
shortname = fields.CharField(max_length=30)
sport = fields.ReferenceField(Sport, blank=True)
def __str__(self):
"""Returns printable version of Team object."""
return self.name
def __repr__(self):
"""Returns printable version of Team object."""
return "Team({0}{1})".format(self.name, self.shortname)
class Meta:
"""Sets the connection and connections details."""
connection_alias = 'kontor'
write_concern = WriteConcern(j=True)
class Player(MongoModel):
"""
Class Player represents a player.
"""
first_name = fields.CharField(max_length=60)
last_name = fields.CharField(max_length=60)
def __str__(self):
"""Returns printable version of Team object."""
return "{0} {1}".format(self.first_name, self.last_name)
def __repr__(self):
"""Returns printable version of Team object."""
return "Player({0} {1})".format(self.first_name, self.last_name)
class Meta:
"""Sets the connection and connections details."""
connection_alias = 'kontor'
write_concern = WriteConcern(j=True)
class Manufacturer(MongoModel):
"""
Class Manufacturer represents a manufacturer of trading cards.
"""
name = fields.CharField()
class Meta:
"""Sets the connection and connections details."""
connection_alias = 'kontor'
write_concern = WriteConcern(j=True)
class CardSet(MongoModel):
"""
Class CardSet represents the regular card set.
"""
name = fields.CharField()
manufacturer = fields.ReferenceField(Manufacturer)
class Meta:
"""Sets the connection and connections details."""
connection_alias = 'kontor'
write_concern = WriteConcern(j=True)
class ParallelSet(MongoModel):
"""
Class CardSet represents the parallel card set.
"""
name = fields.CharField()
manufacturer = fields.ReferenceField(Manufacturer)
class Meta:
"""Sets the connection and connections details."""
connection_alias = 'kontor'
write_concern = WriteConcern(j=True)
class InsertSet(MongoModel):
"""
Class CardSet represents the inserts card set.
"""
name = fields.CharField()
manufacturer = fields.ReferenceField(Manufacturer)
class Meta:
"""Sets the connection and connections details."""
connection_alias = 'kontor'
write_concern = WriteConcern(j=True)
class Card(MongoModel):
"""
Class CardSet represents the regular card set.
"""
# pylint: disable=too-many-instance-attributes
# Nine is reasonable in this case.
player = fields.ReferenceField(Player)
team = fields.ReferenceField(Team)
manufacturer = fields.ReferenceField(Manufacturer)
card_set = fields.ReferenceField(CardSet, blank=True)
parallel_set = fields.ReferenceField(ParallelSet, blank=True)
insert_set = fields.ReferenceField(InsertSet, blank=True)
rookie = fields.BooleanField(default=False)
year = fields.IntegerField(min_value=1956, max_value=2020)
number = fields.IntegerField()
class Meta:
"""Sets the connection and connections details."""
connection_alias = 'kontor'
write_concern = WriteConcern(j=True)
+405
View File
@@ -0,0 +1,405 @@
"""
Define BLueprint and Views for TradeYourSportsCards
"""
from flask import Blueprint, url_for, render_template, redirect, flash
from flask_login import login_required
from bson import ObjectId
from pymongo.errors import PyMongoError
from .forms import SportForm, TeamForm, PlayerForm, PositionForm
from .forms import ManufacturerForm
from .models import Sport, Team, Player, Position
from .models import Manufacturer, CardSet, ParallelSet, InsertSet, Card
TYSC = Blueprint('tysc', __name__)
@TYSC.route('/sport')
@login_required
def list_sports():
"""
List sports.
:return:
"""
sports = Sport.objects.all()
return render_template('tysc/sports.html', sports=sports, title="Sports")
@TYSC.route('/sport/edit/<sport_id>', methods=['GET', 'POST'])
@login_required
def edit_sport(sport_id):
"""
Edit a sport
"""
sport = Sport.objects.get({'_id': ObjectId(sport_id)})
form = SportForm(obj=sport)
if form.validate_on_submit():
sport.name = form.name.data
sport.save()
flash('You have successfully edited the sport.')
return redirect(url_for('tysc.list_sports'))
form.name.data = sport.name
return render_template('simpleform.html', action="Edit",
form=form, sport=sport, title="Edit Sport")
@TYSC.route('/sport/add', methods=['GET', 'POST'])
@login_required
def add_sport():
"""
Add a sport
:return:
"""
form = SportForm()
if form.validate_on_submit():
sport = Sport()
sport.name = form.name.data
try:
sport.save()
flash('You have successfully added a new sport.')
except PyMongoError:
flash('Error: sport name already exists.')
return redirect(url_for('tysc.list_sports'))
return render_template('simpleform.html', action="Add",
form=form, title="Add Sport")
@TYSC.route('/sport/delete/<sport_id>', methods=['GET', 'POST'])
@login_required
def delete_sport(sport_id):
"""
Delete a sport
:param sport_id:
:return:
"""
sport = Sport.objects.raw({'_id': ObjectId(sport_id)})
if sport:
sport.delete()
flash('You have successfully deleted the sport.')
return redirect(url_for('tysc.list_sports'))
@TYSC.route('/team')
@login_required
def list_teams():
"""
List teams.
:return:
"""
teams = Team.objects.all()
return render_template('tysc/teams.html', teams=teams, title="Teams")
@TYSC.route('/team/edit/<team_id>', methods=['GET', 'POST'])
@login_required
def edit_team(team_id):
"""
Edit a team
"""
team = Team.objects.get({'_id': ObjectId(team_id)})
form = TeamForm(obj=team)
form.sport.choices = [(s.pk, s.name) for s in Sport.objects.all()]
if team.sport:
form.sport.default = team.sport.pk
form.sport.process_data(team.sport.pk)
if form.validate_on_submit():
team.name = form.name.data
team.shortname = form.shortname.data
team.sport = form.sport.data
team.save()
flash('You have successfully edited the team.')
return redirect(url_for('tysc.list_teams'))
form.name.data = team.name
return render_template('simpleform.html', action="Edit",
form=form, team=team, title="Edit Team")
@TYSC.route('/team/add', methods=['GET', 'POST'])
@login_required
def add_team():
"""
Add a team
:return:
"""
form = TeamForm()
form.sport.choices = [(s.pk, s.name) for s in Sport.objects.all()]
if form.validate_on_submit():
team = Team()
team.name = form.name.data
team.shortname = form.shortname.data
team.sport = form.sport.data
try:
team.save()
flash('You have successfully added a new team.')
except PyMongoError:
flash('Error: team name already exists.')
return redirect(url_for('tysc.list_teams'))
return render_template('simpleform.html', action="Add",
form=form, title="Add Team")
@TYSC.route('/team/delete/<team_id>', methods=['GET', 'POST'])
@login_required
def delete_team(team_id):
"""
Delete a team
:param team_id:
:return:
"""
team = Team.objects.raw({'_id': ObjectId(team_id)})
if team:
team.delete()
return redirect(url_for('tysc.list_teams'))
@TYSC.route('/position')
@login_required
def list_positions():
"""
List positions.
:return:
"""
positions = Position.objects.all()
return render_template('tysc/positions.html', positions=positions, title="Positions")
@TYSC.route('/position/edit/<position_id>', methods=['GET', 'POST'])
@login_required
def edit_position(position_id):
"""
Edit a position
"""
position = Position.objects.get({'_id': ObjectId(position_id)})
form = PositionForm(obj=position)
form.sport.choices = [(s.pk, s.name) for s in Sport.objects.all()]
if position.sport:
form.sport.default = position.sport.pk
form.sport.process_data(position.sport.pk)
if form.validate_on_submit():
position.name = form.name.data
position.description = form.description.data
position.sport = form.sport.data
position.save()
flash('You have successfully edited the position.')
return redirect(url_for('tysc.list_positions'))
form.name.data = position.name
form.description.data = position.description
return render_template('simpleform.html', action="Edit",
form=form, position=position, title="Edit position")
@TYSC.route('/position/add', methods=['GET', 'POST'])
@login_required
def add_position():
"""
Add a position
:return:
"""
form = PositionForm()
form.sport.choices = [(s.pk, s.name) for s in Sport.objects.all()]
if form.validate_on_submit():
position = Position()
position.name = form.name.data
position.description = form.description.data
position.sport = form.sport.data
try:
position.save()
flash('You have successfully added a new position.')
except PyMongoError:
flash('Error: team position already exists.')
return redirect(url_for('tysc.list_positions'))
return render_template('simpleform.html', action="Add",
form=form, title="Add Position")
@TYSC.route('/position/delete/<position_id>', methods=['GET', 'POST'])
@login_required
def delete_position(position_id):
"""
Delete a position
:param position_id:
:return:
"""
position = Position.objects.raw({'_id': ObjectId(position_id)})
if position:
position.delete()
return redirect(url_for('tysc.list_positions'))
@TYSC.route('/player')
@login_required
def list_players():
"""
List players.
:return:
"""
players = Player.objects.all()
return render_template('tysc/players.html', players=players, title="Players")
@TYSC.route('/player/edit/<player_id>', methods=['GET', 'POST'])
@login_required
def edit_player(player_id):
"""
Edit a player
"""
player = Player.objects.get({'_id': ObjectId(player_id)})
form = PlayerForm(obj=player)
if form.validate_on_submit():
player.first_name = form.first_name.data
player.last_name = form.last_name.data
player.save()
flash('You have successfully edited the player.')
return redirect(url_for('tysc.list_players'))
form.first_name.data = player.first_name
form.last_name.data = player.last_name
return render_template('simpleform.html', action="Edit",
form=form, player=player, title="Edit player")
@TYSC.route('/player/add', methods=['GET', 'POST'])
@login_required
def add_player():
"""
Add a player
:return:
"""
form = PlayerForm()
if form.validate_on_submit():
player = Player()
player.first_name = form.first_name.data
player.last_name = form.last_name.data
try:
player.save()
flash('You have successfully added a new player.')
except PyMongoError:
flash('Error: player name already exists.')
return redirect(url_for('tysc.list_players'))
return render_template('simpleform.html', action="Add",
form=form, title="Add player")
@TYSC.route('/player/delete/<player_id>', methods=['GET', 'POST'])
@login_required
def delete_player(player_id):
"""
Delete a player
:param player_id:
:return:
"""
player = Player.objects.raw({'_id': ObjectId(player_id)})
if player:
player.delete()
return redirect(url_for('tysc.list_players'))
@TYSC.route('/manufacturer')
@login_required
def list_manufacturers():
"""
List manufacturers.
:return:
"""
manufacturers = Manufacturer.objects.all()
return render_template('tysc/manufacturers.html',
manufacturers=manufacturers,
title="Manufacturers")
@TYSC.route('/manufacturer/edit/<manufacturer_id>', methods=['GET', 'POST'])
@login_required
def edit_manufacturer(manufacturer_id):
"""
Edit a manufacturer
"""
manufacturer = Manufacturer.objects.get({'_id': ObjectId(manufacturer_id)})
form = ManufacturerForm(obj=manufacturer)
if form.validate_on_submit():
manufacturer.name = form.name.data
manufacturer.save()
flash('You have successfully edited the manufacturer.')
return redirect(url_for('tysc.list_manufacturers'))
form.name.data = manufacturer.name
return render_template('simpleform.html', action="Edit",
form=form, manufacturer=manufacturer, title="Edit Manufacturer")
@TYSC.route('/manufacturer/add', methods=['GET', 'POST'])
@login_required
def add_manufacturer():
"""
Add a manufacturer
:return:
"""
form = ManufacturerForm()
if form.validate_on_submit():
manufacturer = Manufacturer()
manufacturer.name = form.name.data
try:
manufacturer.save()
flash('You have successfully added a new manufacturer.')
except PyMongoError:
flash('Error: manufacturer name already exists.')
return redirect(url_for('tysc.list_manufacturers'))
return render_template('simpleform.html', action="Add",
form=form, title="Add Manufacturer")
@TYSC.route('/manufacturer/delete/<manufacturer_id>', methods=['GET', 'POST'])
@login_required
def delete_manufacturer(manufacturer_id):
"""
Delete a manufacturer
:param manufacturer_id:
:return:
"""
manufacturer = Manufacturer.objects.raw({'_id': ObjectId(manufacturer_id)})
if manufacturer:
manufacturer.delete()
flash('You have successfully deleted the manufacturer.')
return redirect(url_for('tysc.list_manufacturers'))
@TYSC.route('/cardsets')
@login_required
def list_card_sets():
"""
List card sets.
:return:
"""
card_sets = CardSet.objects.all()
return render_template('tysc/cardsets.html', card_sets=card_sets, title="Card Sets")
@TYSC.route('/parallelsets')
@login_required
def list_parallel_sets():
"""
List card sets.
:return:
"""
parallel_sets = ParallelSet.objects.all()
return render_template('tysc/parallelsets.html',
parallel_sets=parallel_sets, title="Parallel Sets")
@TYSC.route('/insertsets')
@login_required
def list_insert_sets():
"""
List card sets.
:return:
"""
insert_sets = InsertSet.objects.all()
return render_template('tysc/insertsets.html', insert_sets=insert_sets, title="Inserts")
@TYSC.route('/cards')
@login_required
def list_cards():
"""
List card sets.
:return:
"""
cards = Card.objects.all()
return render_template('tysc/cards.html', cards=cards, title="Cards")
+2
View File
@@ -0,0 +1,2 @@
"""This module declares the version of the Kontor application."""
__version__ = '0.0.7'
+382
View File
@@ -0,0 +1,382 @@
[MASTER]
# Specify a configuration file.
#rcfile=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
init-hook='sys.path = list(); sys.path.append("./lib/python3.4/site-packages/")'
# Profiled execution.
profile=no
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS
# Pickle collected data for later comparisons.
persistent=yes
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
# Use multiple processes to speed up Pylint.
jobs=1
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code
extension-pkg-whitelist=
# Allow optimization of some AST trees. This will activate a peephole AST
# optimizer, which will apply various small optimizations. For instance, it can
# be used to obtain the result of joining multiple strings with the addition
# operator. Joining a lot of strings can lead to a maximum recursion error in
# Pylint and this flag can prevent that. It has one side effect, the resulting
# AST will be different than the one from reality.
optimize-ast=no
[MESSAGES CONTROL]
# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
confidence=
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time. See also the "--disable" option for examples.
#enable=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once).You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
disable=W1601,W1606,W1604,W1630,E1605,E1604,W1636,W1603,W1633,W1610,W1615,W1626,E1606,W0704,I0020,W1622,E1608,W1612,W1638,W1614,W1616,W1605,W1613,W1634,E1603,I0021,W1640,W1621,W1625,E1602,W1639,W1602,W1620,W1617,W1624,W1609,W1618,E1601,E1607,W1637,W1632,W1629,W1635,W1611,W1623,W1608,W1627,W1628,W1607,W1619,E1101,R0201,R0903,R0801,locally-disabled
[REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html. You can also give a reporter class, eg
# mypackage.mymodule.MyReporterClass.
output-format=parseable
# Put messages in a separate file for each module / package specified on the
# command line instead of printing them on stdout. Reports (if any) will be
# written in a file name "pylint_global.[txt|html]".
files-output=no
# Tells whether to display a full report or only the messages
reports=yes
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Add a comment according to your evaluation note. This is used by the global
# evaluation report (RP0004).
comment=no
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details
#msg-template=
[SPELLING]
# Spelling dictionary name. Available dictionaries: none. To make it working
# install python-enchant package.
spelling-dict=
# List of comma separated words that should not be checked.
spelling-ignore-words=
# A path to a file that contains private dictionary; one word per line.
spelling-private-dict-file=
# Tells whether to store unknown words to indicated private dictionary in
# --spelling-private-dict-file option instead of raising a message.
spelling-store-unknown-words=no
[TYPECHECK]
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis
ignored-modules=
# List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamically set).
ignored-classes=SQLObject
# When zope mode is activated, add a predefined set of Zope acquired attributes
# to generated-members.
zope=no
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E0201 when accessed. Python regular
# expressions are accepted.
generated-members=REQUEST,acl_users,aq_parent
[LOGGING]
# Logging modules to check that the string format arguments are in logging
# function parameter format
logging-modules=logging
[VARIABLES]
# Tells whether we should check for unused import in __init__ files.
init-import=no
# A regular expression matching the name of dummy variables (i.e. expectedly
# not used).
dummy-variables-rgx=_$|dummy
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,_cb
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=100
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
# List of optional constructs for which whitespace checking is disabled
no-space-check=trailing-comma,dict-separator
# Maximum number of lines in a module
max-module-lines=1000
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=
[BASIC]
# Required attributes for module, separated by a comma
#required-attributes=
# List of builtins function names that should not be used, separated by a comma
bad-functions=map,filter
# Good variable names which should always be accepted, separated by a comma
good-names=i,j,k,ex,Run,_
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Include a hint for the correct naming format with invalid-name
include-naming-hint=yes
# Regular expression matching correct class attribute names
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
# Naming hint for class attribute names
class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
# Regular expression matching correct function names
function-rgx=[a-z_][a-z0-9_]{2,30}$
# Naming hint for function names
function-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct attribute names
attr-rgx=[a-z_][a-z0-9_]{2,30}$
# Naming hint for attribute names
attr-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct method names
method-rgx=[a-z_][a-z0-9_]{2,30}$
# Naming hint for method names
method-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct argument names
argument-rgx=[a-z_][a-z0-9_]{2,30}$
# Naming hint for argument names
argument-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct inline iteration names
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
# Naming hint for inline iteration names
inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
# Regular expression matching correct module names
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Naming hint for module names
module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Regular expression matching correct constant names
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
# Naming hint for constant names
const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
# Regular expression matching correct class names
class-rgx=[A-Z_][a-zA-Z0-9]+$
# Naming hint for class names
class-name-hint=[A-Z_][a-zA-Z0-9]+$
# Regular expression matching correct variable names
variable-rgx=[a-z_][a-z0-9_]{2,30}$
# Naming hint for variable names
variable-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=__.*__
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,XXX,TODO
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=4
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=no
[CLASSES]
# List of interface methods to ignore, separated by a comma. This is used for
# instance to not check methods defines in Zope's Interface base class.
#ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs
# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=_asdict,_fields,_replace,_source,_make
[DESIGN]
# Maximum number of arguments for function / method
max-args=5
# Argument names that match this expression will be ignored. Default to name
# with leading underscore
ignored-argument-names=_.*
# Maximum number of locals for function / method body
max-locals=15
# Maximum number of return / yield for function / method body
max-returns=6
# Maximum number of branch for function / method body
max-branches=12
# Maximum number of statements in function / method body
max-statements=50
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of attributes for a class (see R0902).
max-attributes=7
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=stringprep,optparse
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled)
import-graph=
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled)
ext-import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled)
int-import-graph=
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
overgeneral-exceptions=Exception
+11
View File
@@ -0,0 +1,11 @@
flask
flask-login
flask-bootstrap
Flask-WTF
flask-testing
pymodm
nose
nose-htmloutput
coverage
pylint
+38
View File
@@ -0,0 +1,38 @@
from flask_testing import TestCase
from kontor import create_app
from kontor.models import User
class TestBase(TestCase):
def create_app(self):
# pass in test configuration
config_name = 'testing'
app = create_app(config_name)
return app
def setUp(self):
"""
Will be called before every test
"""
# create test admin user
admin = User()
admin.username="admin"
admin.password="admin2016"
admin.is_admin=True
admin.save()
# create test non-admin user
employee = User()
employee.username="test_user"
employee.password="test2016"
employee.save()
def tearDown(self):
"""
Will be called after every test
"""
users = User.objects.all()
for user in users:
user.delete()
+51
View File
@@ -0,0 +1,51 @@
"""
This module cantains tests for the comic data model
"""
import unittest
from . import TestBase
from kontor.comics.models import Artist, Comic, Publisher
class TestComicsModel(TestBase):
"""This TestCase contains tests for comic data model."""
def setUp(self):
publisher = Publisher()
publisher.name = "Publisher1"
publisher.save()
artist = Artist()
artist.name = "Artist1"
artist.save()
comic = Comic()
comic.title = "Title1"
comic.save()
def tearDown(self):
for comic in Comic.objects.all():
comic.delete()
for artist in Artist.objects.all():
artist.delete()
for publisher in Publisher.objects.all():
publisher.delete()
def test_comic_model(self):
comic_list = Comic.objects.all()
self.assertEqual(comic_list.count(), 1)
def test_publisher_model(self):
self.assertEqual(Publisher.objects.all().count(), 1)
def test_artist_model(self):
self.assertEqual(Artist.objects.all().count(), 1)
def test_assign_publisher(self):
comic = Comic.objects.first()
publisher = Publisher.objects.first()
comic.publisher = publisher
comic.save()
self.assertEqual(publisher.name, comic.publisher.name)
self.assertEqual(publisher.comics.count(), 1)
if __name__ == '__main__':
unittest.main()
+44
View File
@@ -0,0 +1,44 @@
"""This module contains tests for Comic related urls."""
import unittest
from flask import url_for
from . import TestBase
class TestComicsViews(TestBase):
def test_comics_index(self):
"""
Test that comics page is inaccessible without login
and redirects to login page then to comics page
"""
target_url = url_for('comic.list_comics')
redirect_url = url_for('auth.login', next=target_url)
response = self.client.get(target_url)
self.assertEqual(response.status_code, 302)
self.assertRedirects(response, redirect_url)
def test_comics_artist(self):
"""
Test that comics page is inaccessible without login
and redirects to login page then to comics page
"""
target_url = url_for('comic.list_artists')
redirect_url = url_for('auth.login', next=target_url)
response = self.client.get(target_url)
self.assertEqual(response.status_code, 302)
self.assertRedirects(response, redirect_url)
def test_comics_publisher(self):
"""
Test that comics page is inaccessible without login
and redirects to login page then to comics page
"""
target_url = url_for('comic.list_publishers')
redirect_url = url_for('auth.login', next=target_url)
response = self.client.get(target_url)
self.assertEqual(response.status_code, 302)
self.assertRedirects(response, redirect_url)
if __name__ == '__main__':
unittest.main()
+4
View File
@@ -0,0 +1,4 @@
# instance/config.py
SECRET_KEY = 'p9Bv<3Eid9%$i01'
+14
View File
@@ -0,0 +1,14 @@
"""
This module cantains tests for the general Kontor data model.
"""
from . import TestBase
from kontor.models import User
class TestKontorModel(TestBase):
"""This TestCase contains tests for users."""
def test_user_model(self):
"""
Test number of records in User collection
"""
self.assertEqual(User.objects.all().count(), 2)
+58
View File
@@ -0,0 +1,58 @@
"""This module contains tests for Comic related urls."""
import unittest
from flask import url_for
from . import TestBase
class TestKontorViews(TestBase):
def test_homepage_view(self):
"""
Test that homepage is accessible without login
"""
response = self.client.get(url_for('home.homepage'))
self.assertEqual(response.status_code, 200)
def test_login_view(self):
"""
Test that login page is accessible without login
"""
response = self.client.get(url_for('auth.login'))
self.assertEqual(response.status_code, 200)
def test_logout_view(self):
"""
Test that logout link is inaccessible without login
and redirects to login page then to logout
"""
target_url = url_for('auth.logout')
redirect_url = url_for('auth.login', next=target_url)
response = self.client.get(target_url)
self.assertEqual(response.status_code, 302)
self.assertRedirects(response, redirect_url)
def test_dashboard_view(self):
"""
Test that dashboard is inaccessible without login
and redirects to login page then to dashboard
"""
target_url = url_for('home.dashboard')
redirect_url = url_for('auth.login', next=target_url)
response = self.client.get(target_url)
self.assertEqual(response.status_code, 302)
self.assertRedirects(response, redirect_url)
def test_admin_dashboard_view(self):
"""
Test that dashboard is inaccessible without login
and redirects to login page then to dashboard
"""
target_url = url_for('home.admin_dashboard')
redirect_url = url_for('auth.login', next=target_url)
response = self.client.get(target_url)
self.assertEqual(response.status_code, 302)
self.assertRedirects(response, redirect_url)
if __name__ == '__main__':
unittest.main()
+49
View File
@@ -0,0 +1,49 @@
"""
This module cantains tests for the comic data model
"""
import unittest
from . import TestBase
from kontor.library.models import Author, Publisher, Book
class TestComicsModel(TestBase):
"""This TestCase contains tests for comic data model."""
def setUp(self):
publisher = Publisher()
publisher.name = "Publisher1"
publisher.save()
author = Author()
author.name = "Autor1"
author.save()
book = Book()
book.title = "Title1"
book.save()
def tearDown(self):
for book in Book.objects.all():
book.delete()
for author in Author.objects.all():
author.delete()
for publisher in Publisher.objects.all():
publisher.delete()
def test_comic_model(self):
book_list = Book.objects.all()
self.assertEqual(book_list.count(), 1)
def test_publisher_model(self):
self.assertEqual(Publisher.objects.all().count(), 1)
def test_artist_model(self):
self.assertEqual(Author.objects.all().count(), 1)
def test_assign_publisher(self):
book = Book.objects.first()
publisher = Publisher.objects.first()
book.publisher = publisher
book.save()
self.assertEqual(publisher.name, book.publisher.name)
if __name__ == '__main__':
unittest.main()
+44
View File
@@ -0,0 +1,44 @@
"""This module contains tests for Comic related urls."""
import unittest
from flask import url_for
from . import TestBase
class TestLibraryViews(TestBase):
def test_library_index(self):
"""
Test that comics page is inaccessible without login
and redirects to login page then to comics page
"""
target_url = url_for('library.list_books')
redirect_url = url_for('auth.login', next=target_url)
response = self.client.get(target_url)
self.assertEqual(response.status_code, 302)
self.assertRedirects(response, redirect_url)
def test_library_author(self):
"""
Test that comics page is inaccessible without login
and redirects to login page then to comics page
"""
target_url = url_for('library.list_authors')
redirect_url = url_for('auth.login', next=target_url)
response = self.client.get(target_url)
self.assertEqual(response.status_code, 302)
self.assertRedirects(response, redirect_url)
def test_library_publisher(self):
"""
Test that comics page is inaccessible without login
and redirects to login page then to comics page
"""
target_url = url_for('library.list_publishers')
redirect_url = url_for('auth.login', next=target_url)
response = self.client.get(target_url)
self.assertEqual(response.status_code, 302)
self.assertRedirects(response, redirect_url)
if __name__ == '__main__':
unittest.main()
+53
View File
@@ -0,0 +1,53 @@
"""
This module cantains tests for the TYSC data model
"""
import unittest
from . import TestBase
from kontor.tysc import initialize_model
from kontor.tysc.models import Sport, Position, Team, Player
from kontor.tysc.models import Manufacturer, CardSet, ParallelSet, InsertSet, Card
class TestTYSCModel(TestBase):
"""
This TestCase contains tests for TYSC data model.
"""
_initialize_db_ = True
def setUp(self):
if self._initialize_db_:
initialize_model()
self._initialize_db_ = False
def tearDown(self):
pass
def test_sport_model(self):
self.assertEqual(Sport.objects.all().count(), 4)
def test_position_model(self):
self.assertEqual(Position.objects.all().count(), 25)
def test_team_model(self):
self.assertEqual(Team.objects.all().count(), 122)
def test_manufacturer_model(self):
self.assertEqual(Manufacturer.objects.all().count(), 8)
def test_cardset_model(self):
self.assertEqual(CardSet.objects.all().count(), 11)
def test_parallelset_model(self):
self.assertEqual(ParallelSet.objects.all().count(), 3)
def test_insertset_model(self):
self.assertEqual(InsertSet.objects.all().count(), 1)
def test_playerset_model(self):
self.assertEqual(Player.objects.all().count(), 36)
def test_card_model(self):
self.assertEqual(Card.objects.all().count(), 36)
if __name__ == '__main__':
unittest.main()
+110
View File
@@ -0,0 +1,110 @@
"""This module contains tests for TradeYourSportsCards related urls."""
import unittest
from flask import url_for
from . import TestBase
class TestTYSCViews(TestBase):
def test_tysc_index(self):
"""
Test that TYSC page is inaccessible without login
and redirects to login page then to TYSC page
"""
target_url = url_for('tysc.list_cards')
redirect_url = url_for('auth.login', next=target_url)
response = self.client.get(target_url)
self.assertEqual(response.status_code, 302)
self.assertRedirects(response, redirect_url)
def test_tysc_sport(self):
"""
Test that TYSC page is inaccessible without login
and redirects to login page then to TYSC page
"""
target_url = url_for('tysc.list_sports')
redirect_url = url_for('auth.login', next=target_url)
response = self.client.get(target_url)
self.assertEqual(response.status_code, 302)
self.assertRedirects(response, redirect_url)
def test_tysc_team(self):
"""
Test that TYSC page is inaccessible without login
and redirects to login page then to TYSC page
"""
target_url = url_for('tysc.list_teams')
redirect_url = url_for('auth.login', next=target_url)
response = self.client.get(target_url)
self.assertEqual(response.status_code, 302)
self.assertRedirects(response, redirect_url)
def test_tysc_player(self):
"""
Test that TYSC page is inaccessible without login
and redirects to login page then to TYSC page
"""
target_url = url_for('tysc.list_players')
redirect_url = url_for('auth.login', next=target_url)
response = self.client.get(target_url)
self.assertEqual(response.status_code, 302)
self.assertRedirects(response, redirect_url)
def test_tysc_manufacturer(self):
"""
Test that TYSC page is inaccessible without login
and redirects to login page then to TYSC page
"""
target_url = url_for('tysc.list_manufacturers')
redirect_url = url_for('auth.login', next=target_url)
response = self.client.get(target_url)
self.assertEqual(response.status_code, 302)
self.assertRedirects(response, redirect_url)
def test_tysc_card_set(self):
"""
Test that TYSC page is inaccessible without login
and redirects to login page then to TYSC page
"""
target_url = url_for('tysc.list_card_sets')
redirect_url = url_for('auth.login', next=target_url)
response = self.client.get(target_url)
self.assertEqual(response.status_code, 302)
self.assertRedirects(response, redirect_url)
def test_tysc_parallel_set(self):
"""
Test that TYSC page is inaccessible without login
and redirects to login page then to TYSC page
"""
target_url = url_for('tysc.list_parallel_sets')
redirect_url = url_for('auth.login', next=target_url)
response = self.client.get(target_url)
self.assertEqual(response.status_code, 302)
self.assertRedirects(response, redirect_url)
def test_tysc_insert_set(self):
"""
Test that TYSC page is inaccessible without login
and redirects to login page then to TYSC page
"""
target_url = url_for('tysc.list_insert_sets')
redirect_url = url_for('auth.login', next=target_url)
response = self.client.get(target_url)
self.assertEqual(response.status_code, 302)
self.assertRedirects(response, redirect_url)
def test_tysc_card(self):
"""
Test that TYSC page is inaccessible without login
and redirects to login page then to TYSC page
"""
target_url = url_for('tysc.list_cards')
redirect_url = url_for('auth.login', next=target_url)
response = self.client.get(target_url)
self.assertEqual(response.status_code, 302)
self.assertRedirects(response, redirect_url)
if __name__ == '__main__':
unittest.main()