Merge branch 'develop/0.1.0' into 'feature/8-create-docker-build'

# Conflicts:
#   springboot/Dockerfile
This commit is contained in:
2025-03-31 18:30:11 +02:00
892 changed files with 45701 additions and 582 deletions
+9
View File
@@ -0,0 +1,9 @@
MIT License
Copyright (c) 2024 Kontor
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+129
View File
@@ -0,0 +1,129 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
+2
View File
@@ -0,0 +1,2 @@
# kontor-bottle
Kontor with Python Bottle Framework
+19
View File
@@ -0,0 +1,19 @@
# set base image (host OS)
FROM python:3.8
# set the working directory in the container
WORKDIR /code
# copy the dependencies file to the working directory
COPY requirements.txt /code/
# copy the content of the local src directory to the working directory
COPY src/ /code/
# install dependencies
RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt
EXPOSE 9000
# command to run on container start
CMD [ "python", "./kontor.py" ]
+2
View File
@@ -0,0 +1,2 @@
pymongo
bottle
+193
View File
@@ -0,0 +1,193 @@
# -*- coding:utf-8 -*-
import pymongo
import comics.publisherDAO
import comics.artistDAO
import comics.comicDAO
import bottle
import cgi
__author__ = 'tpeetz'
class Plugin:
def __init__(self, app, database, sessions):
self.app = app
self.db = database
self.sessions = sessions
self.publishers = publisherDAO.PublisherDAO(database)
self.artists = artistDAO.ArtistDAO(database)
self.comics = comicDAO.ComicDAO(database)
self.routing()
def routing(self):
self.app.route('/comics/', 'GET', self.comic_index)
self.app.route('/comics/comic', 'GET', self.comic_list)
self.app.route('/comics/comic/<id>', 'GET', self.comic_details)
self.app.route('/comics/comic/create', 'GET', self.get_comic_create)
self.app.route('/comics/comic/create', 'POST', self.post_create_comic)
self.app.route('/comics/publisher', 'GET', self.publisher_list)
self.app.route('/comics/publisher/<id>', 'GET', self.publisher_details)
self.app.route('/comics/publisher/create', 'GET', self.get_publisher_create)
self.app.route('/comics/publisher/create', 'POST', self.post_create_publisher)
self.app.route('/comics/artist', 'GET', self.artist_list)
self.app.route('/comics/artist/<id>', 'GET', self.artist_details)
self.app.route('/comics/artist/create', 'GET', self.get_artist_create)
self.app.route('/comics/artist/create', 'POST', self.post_create_artist)
self.app.route('/comics/storyarc', 'GET', self.storyarc_list)
self.app.route('/comics/storyarc/<id>', 'GET', self.storyarc_details)
self.app.route('/comics/storyarc/create', 'GET', self.get_storyarc_create)
self.app.route('/comics/storyarc/create', 'POST', self.post_create_storyarc)
def comic_index(self):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
return bottle.template('comic_index', dict(username=username))
def comic_list(self):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
l = self.comics.get_comics()
return bottle.template('comic_list', dict(comics=l, username=username))
def comic_details(self, id):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
comic = self.comics.get_comic(id)
errors = ""
if comic == None:
errors = "Entry not found"
return bottle.template('comic_template', dict(title=comic['title'],
id=comic['_id'],
current_order=comic['current_order'],
completed=comic['completed'],
errors="",
username=username))
def get_comic_create(self):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
return bottle.template("comic_template", dict(title="",
id='newentry',
current_order=False,
completed=False,
errors="",
username=username))
def post_create_comic(self):
comic_id = bottle.request.forms.get("id")
comic_title = bottle.request.forms.get("title")
comic_order = bottle.request.forms.get("current_order")
comic_completed = bottle.request.forms.get("completed")
if comic_id == "newentry":
self.comics.insert_entry(comic_title, None, comic_order, comic_completed)
else:
self.comics.update_entry(comic_id, comic_title, None, comic_order, comic_completed)
bottle.redirect("/comics/comic")
def publisher_list(self):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
l = self.publishers.get_publishers()
return bottle.template('publisher_list', dict(publishers=l, username=username))
def publisher_details(self, id):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
publisher = self.publishers.get_publisher(id)
errors = ""
if publisher == None:
errors= "Entry not found"
return bottle.template('publisher_template', dict(name=publisher['name'], id=publisher['_id'], errors="", username=username))
def get_publisher_create(self):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
return bottle.template("publisher_template", dict(name="", id='newentry', errors="", username=username))
def post_create_publisher(self):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
publisher_id = bottle.request.forms.get("id")
publisher_name = bottle.request.forms.get("name")
if publisher_id == "newentry":
self.publishers.insert_entry(publisher_name)
else:
self.publishers.update_entry(publisher_id, publisher_name)
bottle.redirect("/comics/publisher")
def artist_list(self):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
l = self.artists.get_artists()
return bottle.template('artist_list', dict(artists=l, username=username))
def artist_details(self, id):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
artist = self.artists.get_artist(id)
errors = ""
if artist == None:
errors= "Entry not found"
return bottle.template('artist_template', dict(name=artist['name'], id=artist['_id'], errors="", username=username))
def get_artist_create(self):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
return bottle.template("artist_template", dict(name="", id='newentry', errors="", username=username))
def post_create_artist(self):
artist_id = bottle.request.forms.get("id")
artist_name = bottle.request.forms.get("name")
if artist_id == "newentry":
self.artists.insert_entry(artist_name)
else:
self.artists.update_entry(artist_id, artist_name)
bottle.redirect("/comics/artist")
def storyarc_list(self):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
l = self.storyarcs.get_storyarcs()
return bottle.template('storyarc_list', dict(storyarcs=l, username=username))
def storyarc_details(self, id):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
storyarc = self.storyarcs.get_storyarc(id)
errors = ""
if storyarc == None:
errors = "Entry not found"
return bottle.template('storyarc_template', dict(title=storyarc['title'], id=storyarc['_id'], errors="", username=username))
def get_storyarc_create(self):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
return bottle.template("storyarc_template", dict(title="", id='newentry', errors="", username=username))
def post_create_storyarc(self):
storyarc_id = bottle.request.forms.get("id")
storyarc_title = bottle.request.forms.get("title")
if storyarc_id == "newentry":
self.storyarcs.insert_entry(storyarc_title)
else:
self.storyarcs.update_entry(storyarc_id, storyarc_title)
bottle.redirect("/comics/storyarc")
+49
View File
@@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
from bson.objectid import ObjectId
import sys
class ArtistDAO:
# constructor for the class
def __init__(self, database):
self.db = database
self.artists = database.artists
def get_artists(self):
cursor = self.artists.find()
l = []
for artist in cursor:
l.append({'name':artist['name'], '_id':artist['_id']})
return l
def get_artist(self, artist_id):
artist = self.artists.find_one({"_id": ObjectId(artist_id)})
return artist
def insert_entry(self, artist_name):
print("inserting artist entry", artist_name)
artist = {"name": artist_name}
# now insert the post
try:
result = self.artists.insert_one(artist)
print("Matching artist: ", result.matched_count)
print("Modified artist: ", result.modified_count)
except:
print("Error inserting artist")
print("Unexpected error:", sys.exc_info())
def update_entry(self, artist_id, artist_name):
print("upserting artist entry", artist_name)
filter_doc = {"_id": ObjectId(artist_id)}
artist = { "$set": {"name": artist_name}}
# now insert the post
try:
result = self.artists.update_one(filter_doc, artist)
print("Modified artist: ", result.modified_count)
except:
print("Error inserting artist")
print("Unexpected error:", sys.exc_info())
+52
View File
@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
from bson.objectid import ObjectId
import sys
class ComicDAO:
# constructor for the class
def __init__(self, database):
self.db = database
self.comics = database.comics
def get_comics(self):
cursor = self.comics.find()
l = []
for comic in cursor:
l.append({'title':comic['title'],
'_id':comic['_id'],
'current_order':comic['current_order'],
'completed':comic['completed']})
return l
def get_comic(self, comic_id):
comic = self.comics.find_one({"_id": ObjectId(comic_id)})
return comic
def insert_entry(self, comic_title, comic_publisher=None, comic_order=False, comic_completed=False):
print("inserting comic entry", comic_title)
comic = {"title": comic_title, "current_order": comic_order, "completed": comic_completed}
# now insert the comic
try:
result = self.comics.insert_one(comic)
print("Modified comic: ", result.modified_count)
except:
print("Error inserting comic")
print("Unexpected error:", sys.exc_info())
def update_entry(self, comic_id, comic_title, comic_publisher=None, comic_order=False, comic_completed=False):
print("upserting comic entry", comic_title)
filter_doc = {"_id": ObjectId(comic_id)}
comic = { "$set": {"title": comic_title, 'current_order': comic_order, 'completed': comic_completed}}
# now insert the post
try:
result = self.comics.update_one(filter_doc, comic)
print("Matching comic: ", result.matched_count)
print("Modified comic: ", result.modified_count)
except:
print("Error inserting comic")
print("Unexpected error:", sys.exc_info())
+121
View File
@@ -0,0 +1,121 @@
# -*- coding: utf-8 -*-
from mongoengine import connect, Document
from mongoengine import StringField, BooleanField
from mongoengine import ListField, ReferenceField, GenericReferenceField
import pprint
class Publisher(Document):
name = StringField(required=True)
def __str__(self):
s = "Publisher(%s)" % self.name
return s
class Artist(Document):
name = StringField(required=True)
className = StringField()
def __str__(self):
s = "Artist(%s)" % self.name
return s
class Issue(Document):
number = StringField()
comic = GenericReferenceField()
is_read = BooleanField(default=False)
is_stock = BooleanField(default=False)
def __str__(self):
s = "Issue(%s # %s, %s)" % (self.comic.title, self.number, self.is_read)
return s
class StoryArc(Document):
name = StringField(required=True)
comic = GenericReferenceField()
issues = ListField(ReferenceField(Issue))
def __str__(self):
s = "StoryArc(%s, %s)" % (self.name, self.comic.title)
return s
class Volume(Document):
name = StringField(required=True)
comic = GenericReferenceField()
issues = ListField(ReferenceField(Issue))
def __str__(self):
s = "Volume(%s, %s, %s)" % (self.id, self.name, self.comic.title)
return s
class Comic(Document):
title = StringField(required=True)
publisher = ReferenceField(Publisher)
current_order = BooleanField()
completed = BooleanField()
issues = ListField(ReferenceField(Issue))
stories = ListField(ReferenceField(StoryArc))
def __str__(self):
if self.publisher is None:
s = "Comic(%s, %s, %s, %s)" % (self.title, self.publisher, self.current_order, self.completed)
return s
else:
s = "Comic(%s, %s, %s, %s)" % (
self.title, self.publisher.name, self.current_order, self.completed)
return s
class TradePaperback(Document):
comic = ReferenceField(Comic)
issue_start = StringField()
issue_end = StringField()
def __str__(self):
s = "TPB(%s)" % self.comic.title
return s
def get_publisher(name):
publisher = Publisher.objects(name=name)
if publisher.count() > 0:
return publisher[0]
else:
return None
def get_comic(title):
comic = Comic.objects(title=title)
if comic.count() > 0:
return comic[0]
else:
return None
def get_issue(title, number):
comic = get_comic(title)
issues = Issue.objects(number=number, comic=comic)
if issues.count() > 0:
return issues[0]
else:
return None
if __name__ == '__main__':
connect('comics')
for publisher in Publisher.objects:
pprint.pprint(publisher)
for artist in Artist.objects:
pprint.pprint(artist)
for comic in Comic.objects:
pprint.pprint(comic)
for issue in Issue.objects:
pprint.pprint(issue)
for story in StoryArc.objects:
pprint.pprint(story)
for tpb in TradePaperback.objects:
pprint.pprint(tpb)
@@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
from bson.objectid import ObjectId
import sys
class PublisherDAO:
# constructor for the class
def __init__(self, database):
self.db = database
self.publishers = database.publishers
def get_publishers(self):
cursor = self.publishers.find()
l = []
for publisher in cursor:
l.append({'name':publisher['name'], '_id':publisher['_id']})
return l
def get_publisher(self, publisher_id):
publisher = self.publishers.find_one({"_id": ObjectId(publisher_id)})
return publisher
def insert_entry(self, publisher_name):
print("inserting publisher entry", publisher_name)
publisher = {"name": publisher_name}
# now insert the post
try:
result = self.publishers.insert_one(publisher)
print("Matching publisher: ", result.matched_count)
print("Modified publisher: ", result.modified_count)
except:
print("Error inserting publisher")
print("Unexpected error:", sys.exc_info())
def update_entry(self, publisher_id, publisher_name):
print("upserting publisher entry", publisher_name)
filter_doc = {"_id": ObjectId(publisher_id)}
publisher = { "$set": {"name": publisher_name}}
# now insert the post
try:
result = self.publishers.update_one(filter_doc, publisher)
print("Modified publisher: ", result.modified_count)
except:
print("Error inserting publisher")
print("Unexpected error:", sys.exc_info())
@@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
from bson.objectid import ObjectId
import sys
class StoryArcDAO:
# constructor for the class
def __init__(self, database):
self.db = database
self.storyarcs = database.storyarcs
def get_storyarcs(self):
cursor = self.storyarcs.find()
l = []
for storyarc in cursor:
l.append({'title': storyarc['title'], '_id': storyarc['_id']})
return l
def get_storyarc(self, storyarc_id):
storyarc = self.storyarcs.find_one({"_id": ObjectId(storyarc_id)})
return storyarc
def insert_entry(self, storyarc_title):
print("inserting publisher entry", storyarc_title)
storyarc = {"name": storyarc_title}
# now insert the post
try:
result = self.storyarcs.insert_one(storyarc)
print("Matching storyarc: ", result.matched_count)
print("Modified storyarc: ", result.modified_count)
except:
print("Error inserting storyarc")
print("Unexpected error:", sys.exc_info())
def update_entry(self, storyarc_id, storyarc_title):
print("upserting storyarc entry", storyarc_title)
filter_doc = {"_id": ObjectId(storyarc_id)}
storyarc = {"$set": {"name": storyarc_title}}
# now insert the post
try:
result = self.storyarcs.update_one(filter_doc, storyarc)
print("Modified storyarc: ", result.modified_count)
except:
print("Error inserting storyarc")
print("Unexpected error:", sys.exc_info())
+89
View File
@@ -0,0 +1,89 @@
body {
font-family: sans-serif;
color: #333333;
padding:4em 0 4em;
}
body,
.wrapper {
margin: 10px auto;
/*max-width: 60em;*/
}
header, nav, nav a, main, section, footer {
border-radius: 0px 0.5em 0.5em;
border: 1px solid;
padding: 10px;
margin: 10px;
}
header {
position:fixed;
top:0px;
left:0px;
right:0px;
text-align:center;
padding:10px;
background: lightgrey;
/*border-bottom: 1px solid #d5d5d5;*/
}
nav {
position: fixed;
padding-top: 10em;
font-size: 0.91em;
float: left;
width: 15em;
padding: 0;
background: lightskyblue;
border-color: skyblue;
}
nav ul {
padding: 0;
}
nav li {
list-style: none;
margin: 0;
padding: 0.1em;
}
nav a {
display: block;
padding: 0.2em 10px;
font-weight: bold;
text-decoration: none;
background-color: skyblue;
color: #333;
}
nav ul a:hover,
nav ul a:active {
color: #fffbf0;
background-color: #dfac20;
}
main {
display: block;
background: lightblue;
border-color: #8a9da8;
margin-left: 15em;
min-width: 16em; /* Mindestbreite (der Überschrift) verhindert Anzeigefehler in modernen Browsern */
}
footer {
position:fixed;
padding: 10px;
margin-top: 10px;
bottom:0;
left: 0;
right:0;
background: lightgrey;
border-color: grey;
}
footer p {
float:right;
margin: 0;
}
+148
View File
@@ -0,0 +1,148 @@
# -*- coding: utf-8 -*-
import pymongo
import sessionDAO
import userDAO
import comics
#import library
import bottle
import cgi
import re
__author__ = 'tpeetz'
app = bottle.Bottle()
def index():
cookie = bottle.request.get_cookie("session")
username = sessions.get_username(cookie)
return bottle.template('kontor', dict(username=username))
def show_signup():
return bottle.template("signup", dict(username="",
password="",
password_error="",
email="",
username_error="",
email_error="",
verify_error =""))
def process_signup():
email = bottle.request.forms.get("email")
username = bottle.request.forms.get("username")
password = bottle.request.forms.get("password")
verify = bottle.request.forms.get("verify")
# set these up in case we have an error case
errors = {'username': cgi.escape(username), 'email': cgi.escape(email)}
if validate_signup(username, password, verify, email, errors):
if not users.add_user(username, password, email):
# this was a duplicate
errors['username_error'] = "Username already in use. Please choose another"
return bottle.template("signup", errors)
session_id = sessions.start_session(username)
print(session_id)
bottle.response.set_cookie("session", session_id)
bottle.redirect("/welcome")
else:
print("user did not validate")
return bottle.template("signup", errors)
def show_login():
return bottle.template('login', dict(username="", password="", login_error=""))
def process_login():
username = bottle.request.forms.get("username")
password = bottle.request.forms.get("password")
print("user submitted ", username, "pass ", password)
user_record = users.validate_login(username, password)
if user_record:
# username is stored in the user collection in the _id key
session_id = sessions.start_session(user_record['_id'])
if session_id is None:
bottle.redirect("/internal_error")
cookie = session_id
# Warning, if you are running into a problem whereby the cookie being set here is
# not getting set on the redirect, you are probably using the experimental version of bottle (.12).
# revert to .11 to solve the problem.
bottle.response.set_cookie("session", cookie)
bottle.redirect("/")
else:
return bottle.template("login", dict(username=cgi.escape(username), password="", login_error="Invalid Login"))
def process_logout():
cookie = bottle.request.get_cookie("session")
sessions.end_session(cookie)
bottle.response.set_cookie("session", "")
bottle.redirect("/")
def send_stylesheet(filename):
return bottle.static_file(filename, root='.', mimetype='text/css')
def setup_routing(app):
app.route('/', 'GET', index)
app.route('/signup', 'GET', show_signup)
app.route('/signup', 'POST', process_signup)
app.route('/login', 'GET', show_login)
app.route('/login', 'POST', process_login)
app.route('/logout', 'GET', process_logout)
app.route('/css/<filename:re:.*\.css>', 'GET', send_stylesheet)
# validates that the user information is valid for new signup, return True of False
# and fills in the error string if there is an issue
def validate_signup(username, password, verify, email, errors):
USER_RE = re.compile(r"^[a-zA-Z0-9_-]{3,20}$")
PASS_RE = re.compile(r"^.{3,20}$")
EMAIL_RE = re.compile(r"^[\S]+@[\S]+\.[\S]+$")
errors['username_error'] = ""
errors['password_error'] = ""
errors['verify_error'] = ""
errors['email_error'] = ""
if not USER_RE.match(username):
errors['username_error'] = "invalid username. try just letters and numbers"
return False
if not PASS_RE.match(password):
errors['password_error'] = "invalid password."
return False
if password != verify:
errors['verify_error'] = "password must match"
return False
if email != "":
if not EMAIL_RE.match(email):
errors['email_error'] = "invalid email address"
return False
return True
setup_routing(app)
connection = pymongo.MongoClient("mongodb://mongodb")
database = connection.kontor
users = userDAO.UserDAO(database)
sessions = sessionDAO.SessionDAO(database)
comics_plugin = comics.Plugin(app, database, sessions)
#library_plugin = library.Plugin(app, database, sessions)
print("starting app Kontor")
bottle.run(app, host="0.0.0.0", port=9000, debug=True)
+64
View File
@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
import sys
import random
import string
# The session Data Access Object handles interactions with the sessions collection
class SessionDAO:
def __init__(self, database):
self.db = database
self.sessions = database.sessions
# will start a new session id by adding a new document to the sessions collection
# returns the sessionID or None
def start_session(self, username):
session_id = self.get_random_str(32)
session = {'username': username, '_id': session_id}
try:
self.sessions.insert_one(session)
except:
print("Unexpected error on start_session:", sys.exc_info()[0])
return None
return str(session['_id'])
# will send a new user session by deleting from sessions table
def end_session(self, session_id):
if session_id is None:
return
self.sessions.delete_one({'_id': session_id})
return
# if there is a valid session, it is returned
def get_session(self, session_id):
if session_id is None:
return None
session = self.sessions.find_one({'_id': session_id})
return session
# get the username of the current session, or None if the session is not valid
def get_username(self, session_id):
session = self.get_session(session_id)
if session is None:
return None
else:
return session['username']
def get_random_str(self, num_chars):
random_string = ""
for i in range(num_chars):
random_string = random_string + random.choice(string.ascii_letters)
return random_string
+75
View File
@@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
import hmac
import random
import string
import hashlib
import pymongo
# The User Data Access Object handles all interactions with the User collection.
class UserDAO:
def __init__(self, db):
self.db = db
self.users = self.db.users
self.SECRET = 'verysecret'
# makes a little salt
def make_salt(self):
salt = ""
for i in range(5):
salt = salt + random.choice(string.ascii_letters)
return salt
# implement the function make_pw_hash(name, pw) that returns a hashed password
# of the format:
# HASH(pw + salt),salt
# use sha256
def make_pw_hash(self, pw,salt=None):
if salt == None:
salt = self.make_salt();
return hashlib.sha256(pw + salt).hexdigest()+","+ salt
# Validates a user login. Returns user record or None
def validate_login(self, username, password):
user = None
try:
user = self.users.find_one({'_id': username})
except:
print("Unable to query database for user")
if user is None:
print("User not in database")
return None
salt = user['password'].split(',')[1]
if user['password'] != self.make_pw_hash(password, salt):
print("user password is not a match")
return None
# Looks good
return user
# creates a new user in the users collection
def add_user(self, username, password, email):
password_hash = self.make_pw_hash(password)
user = {'_id': username, 'password': password_hash}
if email != "":
user['email'] = email
try:
self.users.insert_one(user)
except pymongo.errors.OperationFailure:
print("oops, mongo error")
return False
except pymongo.errors.DuplicateKeyError as e:
print("oops, username is already taken")
return False
return True
@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<ul>
%for artist in artists:
<li><a href="/comics/artist/{{artist['_id']}}">{{artist['name']}}</a></li>
%end
</ul>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<form action="/comics/artist/create" method="POST">
{{errors}}
<h2>Title</h2>
<input type="hidden" name="id", value="{{id}}">
<input type="text" name="name" size="60" value="{{name}}"><br>
<p>
<input type="submit" value="Submit">
</form>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<table border="1">
<tr><td>Title</td><td>Current Order</td><td>Completed</td></tr>
<tr>
%for comic in comics:
<td><a href="/comics/comic/{{comic['_id']}}">{{comic['title']}}</a></td>
<td>{{comic['current_order']}}</td>
<td>{{comic['completed']}}</td>
%end
</tr>
</table>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<form action="/comics/comic/create" method="POST">
{{errors}}
<h2>Title</h2>
<input type="hidden" name="id", value="{{id}}">
<input type="text" name="title" size="60" value="{{title}}"><br>
<p>
<label>
<input type="checkbox" name="current_order" size="60" value="{{current_order}}">
Current Order
</label>
<label>
<input type="checkbox" name="completed" size="60" value="{{completed}}">
Completed
</label>
<p>
<input type="submit" value="Submit">
</form>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
+27
View File
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
</details>
</main>
%if (username == None):
<footer><a href="/login">Login</a><p>Ingenieurb&uuml;ro Thomas Peetz</p></footer>
%end
%if (username != None):
<footer><a href="/logout">{{username}}</a><p>Ingenieurb&uuml;ro Thomas Peetz</p></footer>
%end
</body>
</html>
+52
View File
@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<form method="POST">
<table>
<tr>
<td class="label">Username</td>
<td><input type="text" name="username" value="{{username}}"></td>
<td class="error"></td>
</tr>
<tr>
<td class="label">Password</td>
<td><input type="password" name="password" value=""></td>
<td class="error">{{login_error}}</td>
</tr>
</table>
<input type="submit" value="Submit">
</form>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<ul>
%for publisher in publishers:
<li><a href="/comics/publisher/{{publisher['_id']}}">{{publisher['name']}}</a></li>
%end
</ul>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics/comic">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<form action="/comics/publisher/create" method="POST">
{{errors}}
<h2>Title</h2>
<input type="hidden" name="id", value="{{id}}">
<input type="text" name="name" size="60" value="{{name}}"><br>
<p>
<input type="submit" value="Submit">
</form>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
+59
View File
@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<h2>Signup</h2>
<form method="post">
<table>
<tr><td class="label">Username</td>
<td><input type="text" name="username" value="{{username}}"></td>
<td class="error">{{username_error}}</td>
</tr>
<tr>
<td class="label">Password</td>
<td><input type="password" name="password" value=""></td>
<td class="error">{{password_error}}</td>
</tr>
<tr>
<td class="label">Verify Password</td>
<td><input type="password" name="verify" value=""></td>
<td class="error">{{verify_error}}</td>
</tr>
<tr>
<td class="label">Email (optional)</td>
<td><input type="text" name="email" value="{{email}}"></td>
<td class="error">{{email_error}}</td>
</tr>
</table>
<input type="submit">
</form>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics/comic">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
<li><a href="/comics/storyarc">StoryArcs</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<ul>
%for storyarc in storyarcs:
<li><a href="/comics/storyarc/{{storyarc['_id']}}">{{storyarc['title']}}</a></li>
%end
</ul>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
<li><a href="/comics/storyarc">StoryArcs</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<form action="/comics/storyarc/create" method="POST">
{{errors}}
<h2>Title</h2>
<input type="hidden" name="id", value="{{id}}">
<input type="text" name="title" size="60" value="{{title}}"><br>
<p>
<input type="submit" value="Submit">
</form>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
+31
View File
@@ -0,0 +1,31 @@
services:
mongodb:
image: mongo
restart: always
volumes:
- db-data:/var/lib/mongo
networks:
- backend-network
app:
build: app
restart: always
ports:
- 9000:9000
networks:
- backend-network
- frontend-network
nginx:
image: nginx
restart: always
volumes:
- ./nginx:/etc/nginx/conf.d
- ./front-end:/var/www/front-end
ports:
- 8070:80
networks:
- frontend-network
volumes:
db-data:
networks:
backend-network:
frontend-network:
+82
View File
@@ -0,0 +1,82 @@
<html>
<head>
<title>Hello!</title>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<style>
*, ::after, ::before {
box-sizing: inherit;
background-color: #9eb5c2;
color: rgb(8, 8, 71);
}
.moby {
padding: 2rem 1rem;
margin-bottom: 2rem;
border-radius: .3rem;
border-bottom: 0;
text-align: center;
align-content: center;
align-items: center;
padding: 4rem 2rem;
}
.moby button {
color: #fff;
background-color: rgb(6, 105, 138);
border-color: rgb(6, 105, 138);
padding: 5px;
border-radius: 5px;
margin: 5px;
}
.moby .query {
text-align: left;
border: 3px dotted #e9ecef;
padding: 1.5rem 1.2rem;
max-width: 800px;
min-height: 200px;
margin-left: auto;
margin-right: auto;
border-radius: .3rem;
}
.moby .query button:hover{
cursor: pointer;
background-color: #73abff;
}
</style>
</head>
<body>
<div class="moby">
<img src="https://www.docker.com/sites/default/files/Whale%20Logo332_5.png" />
<h1>Hello there!</h1>
<p>Simple DEV environment setup with Docker and Docker Compose</p>
<div class="query">
<p>Set url path(default is '/'), then query the app service.</p>
<div>
<button type="submit" onclick="queryServer()">GET</button>
<input id="path" type="text">
</div>
<h3>Server response</h3>
<div id="response">
</div>
</div>
</div>
<script type="text/javascript">
function queryServer() {
const Http = new XMLHttpRequest();
const path = document.getElementById('path').value;
const url = "/";
Http.open("GET", url + path);
Http.send();
Http.onreadystatechange = (e) => {
document.getElementById('response').innerHTML = Http.responseText;
}
}
</script>
</body>
</html>
+14
View File
@@ -0,0 +1,14 @@
server {
listen 80;
server_name localhost;
root /var/www/front-end;
location /api {
proxy_pass http://app:9000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
+12
View File
@@ -0,0 +1,12 @@
.idea/
__pycache__/
bin/
include/
lib/
lib64
pip-selfcheck.json
*.pyc
kontor.properties
*.nja
dist/
kontor_bottle.egg-info/
+13
View File
@@ -0,0 +1,13 @@
node {
stage("Checkout") {
checkout scm
}
stage("setup virualenv") {
sh "virtualenv ."
sh "source bin/activate; pip install bottle pymongo"
}
stage("build") {
sh "source bin/activate; python setup.py sdist"
}
}
+2
View File
@@ -0,0 +1,2 @@
# Kontor Bottle
+196
View File
@@ -0,0 +1,196 @@
# -*- coding:utf-8 -*-
import pymongo
import publisherDAO
import artistDAO
import comicDAO
import storyArcDAO
import bottle
import cgi
__author__ = 'tpeetz'
class Plugin:
def __init__(self, app, database, sessions):
self.app = app
self.db = database
self.sessions = sessions
self.publishers = publisherDAO.PublisherDAO(database)
self.artists = artistDAO.ArtistDAO(database)
self.comics = comicDAO.ComicDAO(database)
self.storyarcs = storyArcDAO.StoryArcDAO(database)
self.routing()
def routing(self):
self.app.route('/comics', 'GET', self.comic_index)
self.app.route('/comics/', 'GET', self.comic_index)
self.app.route('/comics/comic', 'GET', self.comic_list)
self.app.route('/comics/comic/<id>', 'GET', self.comic_details)
self.app.route('/comics/comic/create', 'GET', self.get_comic_create)
self.app.route('/comics/comic/create', 'POST', self.post_create_comic)
self.app.route('/comics/publisher', 'GET', self.publisher_list)
self.app.route('/comics/publisher/<id>', 'GET', self.publisher_details)
self.app.route('/comics/publisher/create', 'GET', self.get_publisher_create)
self.app.route('/comics/publisher/create', 'POST', self.post_create_publisher)
self.app.route('/comics/artist', 'GET', self.artist_list)
self.app.route('/comics/artist/<id>', 'GET', self.artist_details)
self.app.route('/comics/artist/create', 'GET', self.get_artist_create)
self.app.route('/comics/artist/create', 'POST', self.post_create_artist)
self.app.route('/comics/storyarc', 'GET', self.storyarc_list)
self.app.route('/comics/storyarc/<id>', 'GET', self.storyarc_details)
self.app.route('/comics/storyarc/create', 'GET', self.get_storyarc_create)
self.app.route('/comics/storyarc/create', 'POST', self.post_create_storyarc)
def comic_index(self):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
return bottle.template('comic_index', dict(username=username))
def comic_list(self):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
l = self.comics.get_comics()
return bottle.template('comic_list', dict(comics=l, username=username))
def comic_details(self, id):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
comic = self.comics.get_comic(id)
errors = ""
if comic == None:
errors = "Entry not found"
return bottle.template('comic_template', dict(title=comic['title'],
id=comic['_id'],
current_order=comic['current_order'],
completed=comic['completed'],
errors="",
username=username))
def get_comic_create(self):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
return bottle.template("comic_template", dict(title="",
id='newentry',
current_order=False,
completed=False,
errors="",
username=username))
def post_create_comic(self):
comic_id = bottle.request.forms.get("id")
comic_title = bottle.request.forms.get("title")
comic_order = bottle.request.forms.get("current_order")
comic_completed = bottle.request.forms.get("completed")
if comic_id == "newentry":
self.comics.insert_entry(comic_title, None, comic_order, comic_completed)
else:
self.comics.update_entry(comic_id, comic_title, None, comic_order, comic_completed)
bottle.redirect("/comics/comic")
def publisher_list(self):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
l = self.publishers.get_publishers()
return bottle.template('publisher_list', dict(publishers=l, username=username))
def publisher_details(self, id):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
publisher = self.publishers.get_publisher(id)
errors = ""
if publisher == None:
errors= "Entry not found"
return bottle.template('publisher_template', dict(name=publisher['name'], id=publisher['_id'], errors="", username=username))
def get_publisher_create(self):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
return bottle.template("publisher_template", dict(name="", id='newentry', errors="", username=username))
def post_create_publisher(self):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
publisher_id = bottle.request.forms.get("id")
publisher_name = bottle.request.forms.get("name")
if publisher_id == "newentry":
self.publishers.insert_entry(publisher_name)
else:
self.publishers.update_entry(publisher_id, publisher_name)
bottle.redirect("/comics/publisher")
def artist_list(self):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
l = self.artists.get_artists()
return bottle.template('artist_list', dict(artists=l, username=username))
def artist_details(self, id):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
artist = self.artists.get_artist(id)
errors = ""
if artist == None:
errors= "Entry not found"
return bottle.template('artist_template', dict(name=artist['name'], id=artist['_id'], errors="", username=username))
def get_artist_create(self):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
return bottle.template("artist_template", dict(name="", id='newentry', errors="", username=username))
def post_create_artist(self):
artist_id = bottle.request.forms.get("id")
artist_name = bottle.request.forms.get("name")
if artist_id == "newentry":
self.artists.insert_entry(artist_name)
else:
self.artists.update_entry(artist_id, artist_name)
bottle.redirect("/comics/artist")
def storyarc_list(self):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
l = self.storyarcs.get_storyarcs()
return bottle.template('storyarc_list', dict(storyarcs=l, username=username))
def storyarc_details(self, id):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
storyarc = self.storyarcs.get_storyarc(id)
errors = ""
if storyarc == None:
errors = "Entry not found"
return bottle.template('storyarc_template', dict(title=storyarc['title'], id=storyarc['_id'], errors="", username=username))
def get_storyarc_create(self):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
return bottle.template("storyarc_template", dict(title="", id='newentry', errors="", username=username))
def post_create_storyarc(self):
storyarc_id = bottle.request.forms.get("id")
storyarc_title = bottle.request.forms.get("title")
if storyarc_id == "newentry":
self.storyarcs.insert_entry(storyarc_title)
else:
self.storyarcs.update_entry(storyarc_id, storyarc_title)
bottle.redirect("/comics/storyarc")
+50
View File
@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
from bson.objectid import ObjectId
import sys
class ArtistDAO:
# constructor for the class
def __init__(self, database):
self.db = database
self.artists = database.artists
def get_artists(self):
cursor = self.artists.find()
l = []
for artist in cursor:
l.append({'name':artist['name'], '_id':artist['_id']})
return l
def get_artist(self, artist_id):
artist = self.artists.find_one({"_id": ObjectId(artist_id)})
return artist
def insert_entry(self, artist_name):
print "inserting artist entry", artist_name
artist = {"name": artist_name}
# now insert the post
try:
result = self.artists.insert_one(artist)
print "Matching artist: ", result.matched_count
print "Modified artist: ", result.modified_count
except:
print "Error inserting artist"
print "Unexpected error:", sys.exc_info()
def update_entry(self, artist_id, artist_name):
print "upserting artist entry", artist_name
filter_doc = {"_id": ObjectId(artist_id)}
artist = { "$set": {"name": artist_name}}
# now insert the post
try:
result = self.artists.update_one(filter_doc, artist)
print "Modified artist: ", result.modified_count
except:
print "Error inserting artist"
print "Unexpected error:", sys.exc_info()
+53
View File
@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
from bson.objectid import ObjectId
import sys
class ComicDAO:
# constructor for the class
def __init__(self, database):
self.db = database
self.comics = database.comics
def get_comics(self):
cursor = self.comics.find()
l = []
for comic in cursor:
l.append({'title':comic['title'],
'_id':comic['_id'],
'current_order':comic['current_order'],
'completed':comic['completed']})
return l
def get_comic(self, comic_id):
comic = self.comics.find_one({"_id": ObjectId(comic_id)})
return comic
def insert_entry(self, comic_title, comic_publisher=None, comic_order=False, comic_completed=False):
print "inserting comic entry", comic_title
comic = {"title": comic_title, "current_order": comic_order, "completed": comic_completed}
# now insert the comic
try:
result = self.comics.insert_one(comic)
print "Modified comic: ", result.modified_count
except:
print "Error inserting comic"
print "Unexpected error:", sys.exc_info()
def update_entry(self, comic_id, comic_title, comic_publisher=None, comic_order=False, comic_completed=False):
print "upserting comic entry", comic_title
filter_doc = {"_id": ObjectId(comic_id)}
comic = { "$set": {"title": comic_title, 'current_order': comic_order, 'completed': comic_completed}}
# now insert the post
try:
result = self.comics.update_one(filter_doc, comic)
print "Matching comic: ", result.matched_count
print "Modified comic: ", result.modified_count
except:
print "Error inserting comic"
print "Unexpected error:", sys.exc_info()
+121
View File
@@ -0,0 +1,121 @@
# -*- coding: utf-8 -*-
from mongoengine import connect, Document
from mongoengine import StringField, BooleanField
from mongoengine import ListField, ReferenceField, GenericReferenceField
import pprint
class Publisher(Document):
name = StringField(required=True)
def __str__(self):
s = "Publisher(%s)" % self.name
return s
class Artist(Document):
name = StringField(required=True)
className = StringField()
def __str__(self):
s = "Artist(%s)" % self.name
return s
class Issue(Document):
number = StringField()
comic = GenericReferenceField()
is_read = BooleanField(default=False)
is_stock = BooleanField(default=False)
def __str__(self):
s = "Issue(%s # %s, %s)" % (self.comic.title, self.number, self.is_read)
return s
class StoryArc(Document):
name = StringField(required=True)
comic = GenericReferenceField()
issues = ListField(ReferenceField(Issue))
def __str__(self):
s = "StoryArc(%s, %s)" % (self.name, self.comic.title)
return s
class Volume(Document):
name = StringField(required=True)
comic = GenericReferenceField()
issues = ListField(ReferenceField(Issue))
def __str__(self):
s = "Volume(%s, %s, %s)" % (self.id, self.name, self.comic.title)
return s
class Comic(Document):
title = StringField(required=True)
publisher = ReferenceField(Publisher)
current_order = BooleanField()
completed = BooleanField()
issues = ListField(ReferenceField(Issue))
stories = ListField(ReferenceField(StoryArc))
def __str__(self):
if self.publisher is None:
s = "Comic(%s, %s, %s, %s)" % (self.title, self.publisher, self.current_order, self.completed)
return s
else:
s = "Comic(%s, %s, %s, %s)" % (
self.title, self.publisher.name, self.current_order, self.completed)
return s
class TradePaperback(Document):
comic = ReferenceField(Comic)
issue_start = StringField()
issue_end = StringField()
def __str__(self):
s = "TPB(%s)" % self.comic.title
return s
def get_publisher(name):
publisher = Publisher.objects(name=name)
if publisher.count() > 0:
return publisher[0]
else:
return None
def get_comic(title):
comic = Comic.objects(title=title)
if comic.count() > 0:
return comic[0]
else:
return None
def get_issue(title, number):
comic = get_comic(title)
issues = Issue.objects(number=number, comic=comic)
if issues.count() > 0:
return issues[0]
else:
return None
if __name__ == '__main__':
connect('comics')
for publisher in Publisher.objects:
pprint.pprint(publisher)
for artist in Artist.objects:
pprint.pprint(artist)
for comic in Comic.objects:
pprint.pprint(comic)
for issue in Issue.objects:
pprint.pprint(issue)
for story in StoryArc.objects:
pprint.pprint(story)
for tpb in TradePaperback.objects:
pprint.pprint(tpb)
+50
View File
@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
from bson.objectid import ObjectId
import sys
class PublisherDAO:
# constructor for the class
def __init__(self, database):
self.db = database
self.publishers = database.publishers
def get_publishers(self):
cursor = self.publishers.find()
l = []
for publisher in cursor:
l.append({'name':publisher['name'], '_id':publisher['_id']})
return l
def get_publisher(self, publisher_id):
publisher = self.publishers.find_one({"_id": ObjectId(publisher_id)})
return publisher
def insert_entry(self, publisher_name):
print "inserting publisher entry", publisher_name
publisher = {"name": publisher_name}
# now insert the post
try:
result = self.publishers.insert_one(publisher)
print "Matching publisher: ", result.matched_count
print "Modified publisher: ", result.modified_count
except:
print "Error inserting publisher"
print "Unexpected error:", sys.exc_info()
def update_entry(self, publisher_id, publisher_name):
print "upserting publisher entry", publisher_name
filter_doc = {"_id": ObjectId(publisher_id)}
publisher = { "$set": {"name": publisher_name}}
# now insert the post
try:
result = self.publishers.update_one(filter_doc, publisher)
print "Modified publisher: ", result.modified_count
except:
print "Error inserting publisher"
print "Unexpected error:", sys.exc_info()
+49
View File
@@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
from bson.objectid import ObjectId
import sys
class StoryArcDAO:
# constructor for the class
def __init__(self, database):
self.db = database
self.storyarcs = database.storyarcs
def get_storyarcs(self):
cursor = self.storyarcs.find()
l = []
for storyarc in cursor:
l.append({'title': storyarc['title'], '_id': storyarc['_id']})
return l
def get_storyarc(self, storyarc_id):
storyarc = self.storyarcs.find_one({"_id": ObjectId(storyarc_id)})
return storyarc
def insert_entry(self, storyarc_title):
print "inserting publisher entry", storyarc_title
storyarc = {"name": storyarc_title}
# now insert the post
try:
result = self.storyarcs.insert_one(storyarc)
print "Matching storyarc: ", result.matched_count
print "Modified storyarc: ", result.modified_count
except:
print "Error inserting storyarc"
print "Unexpected error:", sys.exc_info()
def update_entry(self, storyarc_id, storyarc_title):
print "upserting storyarc entry", storyarc_title
filter_doc = {"_id": ObjectId(storyarc_id)}
storyarc = {"$set": {"name": storyarc_title}}
# now insert the post
try:
result = self.storyarcs.update_one(filter_doc, storyarc)
print "Modified storyarc: ", result.modified_count
except:
print "Error inserting storyarc"
print "Unexpected error:", sys.exc_info()
+28
View File
@@ -0,0 +1,28 @@
# -*- coding:utf-8 -*-
import pymongo
import bottle
import cgi
__author__ = 'tpeetz'
class Plugin:
def __init__(self, app, database, sessions):
self.app = app
self.db = database
self.sessions = sessions
self.routing()
def routing(self):
self.app.route('/office', 'GET', self.office_index)
self.app.route('/office/travel', 'GET', self.office_index)
def office_index(self):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
return bottle.template('office_index', dict(username=username))
+109
View File
@@ -0,0 +1,109 @@
body {
font-family: sans-serif;
color: #333333;
padding: 3em 0 4em;
}
body,
.wrapper {
margin: 10px auto;
/*max-width: 60em;*/
}
header, main, section, footer {
border-radius: 0px 0.5em 0.5em;
border: 1px solid;
padding: 10px;
margin: 10px;
}
header {
position:fixed;
top:0px;
left:0px;
right:0px;
text-align:center;
padding: 10px;
background: lightgrey;
/*border-bottom: 1px solid #d5d5d5;*/
}
nav {
/*position: fixed;*/
/*padding-top: 10em; */
border-radius: 0px 0.5em 0.5em;
border: 1px solid;
padding: 0;
margin: 10px;
font-size: 0.91em;
float: left;
width: 15em;
background: lightskyblue;
border-color: skyblue;
}
nav ul {
padding: 0;
}
nav li {
list-style: none;
margin: 0.4em;
padding: 0;
}
nav ul ul {
margin: 0 0 0 2em;
padding: 0;
border: none;
}
nav ul ul li {
margin: 0.3em 0;
}
nav a {
display: block;
padding: 0.4em;
text-decoration: none;
font-weight: bold;
border: 1px solid blue;
border-radius: 10px;
box-shadow: 0px 5px 10px white inset;
background-color: skyblue;
color: #333;
}
nav a:focus,
nav a:hover {
color: royalblue;
background-color: gold;
}
main {
display: block;
background: lightblue;
border-color: #8a9da8;
margin-left: 15em;
min-width: 16em; /* Mindestbreite (der Überschrift) verhindert Anzeigefehler in modernen Browsern */
}
footer {
position:fixed;
padding: 10px;
margin-top: 10px;
bottom:0;
left: 0;
right:0;
background: lightgrey;
border-color: grey;
}
footer p {
float:right;
margin: 0;
}
+177
View File
@@ -0,0 +1,177 @@
# -*- coding: utf-8 -*-
import pymongo
import sessionDAO
import userDAO
import homeoffice
import comics
import library
import medien
import tradingcards
import ConfigParser
import bottle
import cgi
import re
__author__ = 'tpeetz'
app = bottle.Bottle()
def index():
cookie = bottle.request.get_cookie("session")
username = sessions.get_username(cookie)
return bottle.template('kontor', dict(username=username))
def show_signup():
return bottle.template("signup", dict(username="",
password="",
password_error="",
email="",
username_error="",
email_error="",
verify_error =""))
def process_signup():
email = bottle.request.forms.get("email")
username = bottle.request.forms.get("username")
password = bottle.request.forms.get("password")
verify = bottle.request.forms.get("verify")
# set these up in case we have an error case
errors = {'username': cgi.escape(username), 'email': cgi.escape(email)}
if validate_signup(username, password, verify, email, errors):
if not users.add_user(username, password, email):
# this was a duplicate
errors['username_error'] = "Username already in use. Please choose another"
return bottle.template("signup", errors)
session_id = sessions.start_session(username)
print session_id
bottle.response.set_cookie("session", session_id)
bottle.redirect("/welcome")
else:
print "user did not validate"
return bottle.template("signup", errors)
def show_login():
return bottle.template('login', dict(username="", password="", login_error=""))
def process_login():
username = bottle.request.forms.get("username")
password = bottle.request.forms.get("password")
print "user submitted ", username, "pass ", password
user_record = users.validate_login(username, password)
if user_record:
# username is stored in the user collection in the _id key
session_id = sessions.start_session(user_record['_id'])
if session_id is None:
bottle.redirect("/internal_error")
cookie = session_id
# Warning, if you are running into a problem whereby the cookie being set here is
# not getting set on the redirect, you are probably using the experimental version of bottle (.12).
# revert to .11 to solve the problem.
bottle.response.set_cookie("session", cookie)
bottle.redirect("/")
else:
return bottle.template("login", dict(username=cgi.escape(username), password="", login_error="Invalid Login"))
def process_logout():
cookie = bottle.request.get_cookie("session")
sessions.end_session(cookie)
bottle.response.set_cookie("session", "")
bottle.redirect("/")
def send_stylesheet(filename):
return bottle.static_file(filename, root='.', mimetype='text/css')
def setup_routing(app):
app.route('/', 'GET', index)
app.route('/signup', 'GET', show_signup)
app.route('/signup', 'POST', process_signup)
app.route('/login', 'GET', show_login)
app.route('/login', 'POST', process_login)
app.route('/logout', 'GET', process_logout)
app.route('/css/<filename:re:.*\.css>', 'GET', send_stylesheet)
# validates that the user information is valid for new signup, return True of False
# and fills in the error string if there is an issue
def validate_signup(username, password, verify, email, errors):
USER_RE = re.compile(r"^[a-zA-Z0-9_-]{3,20}$")
PASS_RE = re.compile(r"^.{3,20}$")
EMAIL_RE = re.compile(r"^[\S]+@[\S]+\.[\S]+$")
errors['username_error'] = ""
errors['password_error'] = ""
errors['verify_error'] = ""
errors['email_error'] = ""
if not USER_RE.match(username):
errors['username_error'] = "invalid username. try just letters and numbers"
return False
if not PASS_RE.match(password):
errors['password_error'] = "invalid password."
return False
if password != verify:
errors['verify_error'] = "password must match"
return False
if email != "":
if not EMAIL_RE.match(email):
errors['email_error'] = "invalid email address"
return False
return True
setup_routing(app)
config = ConfigParser.ConfigParser()
config.read('kontor.properties')
server = config.get('host', 'server')
port = config.get('host', 'port')
db_server = config.get('database', 'server')
db_port = config.get('database', 'port')
db_admin = config.get('database', 'adminDB')
db_user = config.get('database', 'user')
db_password = config.get('database', 'password')
connection_string = "mongodb://"
if db_admin:
connection_string += db_user
connection_string += ':'
connection_string += db_password
connection_string += '@'
connection_string += db_server
connection_string += '?/authsource='
connection_string += db_admin
else:
connection_string += db_server
connection = pymongo.MongoClient(connection_string)
database = connection.kontor
users = userDAO.UserDAO(database)
sessions = sessionDAO.SessionDAO(database)
office_plugin = homeoffice.Plugin(app, database, sessions)
comics_plugin = comics.Plugin(app, database, sessions)
library_plugin = library.Plugin(app, database, sessions)
medien_plugin = medien.Plugin(app, database, sessions)
tradingcards_plugin = tradingcards.Plugin(app, database, sessions)
bottle.run(app, host=server, port=port, debug=True, reloader=True)
+66
View File
@@ -0,0 +1,66 @@
# -*- coding:utf-8 -*-
import pymongo
import bookDAO
import bottle
import cgi
__author__ = 'tpeetz'
class Plugin:
def __init__(self, app, database, sessions):
self.app = app
self.db = database
self.sessions = sessions
self.books = bookDAO.BookDAO(database)
self.routing()
def routing(self):
self.app.route('/library', 'GET', self.book_list)
self.app.route('/library/book', 'GET', self.book_list)
self.app.route('/library/book/<id>', 'GET', self.book_details)
self.app.route('/library/book/create', 'GET', self.get_book_create)
self.app.route('/library/book/create', 'POST', self.post_create_book)
def book_list(self):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
l = self.books.get_books()
return bottle.template('book_list', dict(books=l, username=username))
def book_details(self, id):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
book = self.books.get_book(id)
errors = ""
if book == None:
errors = "Entry not found"
return bottle.template('book_template', dict(title=book['title'],
id=book['_id'],
current_order=book['current_order'],
completed=book['completed'],
errors="",
username=username))
def get_book_create(self):
cookie = bottle.rddequest.get_cookie("session")
username = self.sessions.get_username(cookie)
return bottle.template("book_template", dict(title="",
id='newentry',
current_order=False,
completed=False,
errors="",
username=username))
def post_create_book(self):
book_id = bottle.request.forms.get("id")
book_title = bottle.request.forms.get("title")
book_order = bottle.request.forms.get("current_order")
book_completed = bottle.request.forms.get("completed")
if book_id == "newentry":
self.books.insert_entry(book_title, None, book_order, book_completed)
else:
self.comics.update_entry(book_id, book_title, None, book_order, book_completed)
bottle.redirect("/comics/comic")
+56
View File
@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
from bson.objectid import ObjectId
import sys
class BookDAO:
# constructor for the class
def __init__(self, database):
self.db = database
self.books = database.books
def get_books(self):
cursor = self.books.find()
l = []
for book in cursor:
l.append(
{'title':book['title'],
'_id':book['_id'],
'publisher':book['publisher'],
'isbn':book['isbn'],
'author':book['author']}
)
return l
def get_book(self, book_id):
book = self.books.find_one({"_id": ObjectId(book_id)})
return book
def insert_entry(self, book_title):
print "inserting book entry", book_title
book = {"title": book_title}
# now insert the book
try:
result = self.books.insert_one(book)
print "Matching book: ", result.matched_count
print "Modified book: ", result.modified_count
except:
print "Error inserting book"
print "Unexpected error:", sys.exc_info()
def update_entry(self, book_id, book_title):
print "updating book entry", book_title
filter_doc = {"_id": ObjectId(book_id)}
book = { "$set": {"name": book_title}}
# now insert the book
try:
result = self.books.update_one(filter_doc, book)
print "Modified book: ", result.modified_count
except:
print "Error inserting book"
print "Unexpected error:", sys.exc_info()
+25
View File
@@ -0,0 +1,25 @@
# -*- coding:utf-8 -*-
import pymongo
import bottle
import cgi
__author__ = 'tpeetz'
class Plugin:
def __init__(self, app, database, sessions):
self.app = app
self.db = database
self.sessions = sessions
self.routing()
def routing(self):
self.app.route('/medien', 'GET', self.media_index)
def media_index(self):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
return bottle.template('media_index', dict(username=username))
+64
View File
@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
import sys
import random
import string
# The session Data Access Object handles interactions with the sessions collection
class SessionDAO:
def __init__(self, database):
self.db = database
self.sessions = database.sessions
# will start a new session id by adding a new document to the sessions collection
# returns the sessionID or None
def start_session(self, username):
session_id = self.get_random_str(32)
session = {'username': username, '_id': session_id}
try:
self.sessions.insert_one(session)
except:
print "Unexpected error on start_session:", sys.exc_info()[0]
return None
return str(session['_id'])
# will send a new user session by deleting from sessions table
def end_session(self, session_id):
if session_id is None:
return
self.sessions.delete_one({'_id': session_id})
return
# if there is a valid session, it is returned
def get_session(self, session_id):
if session_id is None:
return None
session = self.sessions.find_one({'_id': session_id})
return session
# get the username of the current session, or None if the session is not valid
def get_username(self, session_id):
session = self.get_session(session_id)
if session is None:
return None
else:
return session['username']
def get_random_str(self, num_chars):
random_string = ""
for i in range(num_chars):
random_string = random_string + random.choice(string.ascii_letters)
return random_string
+11
View File
@@ -0,0 +1,11 @@
from setuptools import setup
setup(
name='kontor-bottle',
packages=['comics', 'homeoffice', 'library', 'medien'],
include_package_data=True,
install_requires=[
'bottle',
'pymongo'
],
)
+25
View File
@@ -0,0 +1,25 @@
# -*- coding:utf-8 -*-
import pymongo
import bottle
import cgi
__author__ = 'tpeetz'
class Plugin:
def __init__(self, app, database, sessions):
self.app = app
self.db = database
self.sessions = sessions
self.routing()
def routing(self):
self.app.route('/tradingcards', 'GET', self.tradingcards_index)
def tradingcards_index(self):
cookie = bottle.request.get_cookie("session")
username = self.sessions.get_username(cookie)
return bottle.template('tradingcards_index', dict(username=username))
+77
View File
@@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-
import hmac
import random
import string
import hashlib
import pymongo
# The User Data Access Object handles all interactions with the User collection.
class UserDAO:
def __init__(self, db):
self.db = db
self.users = self.db.users
self.SECRET = 'verysecret'
# makes a little salt
def make_salt(self):
salt = ""
for i in range(5):
salt = salt + random.choice(string.ascii_letters)
return salt
# implement the function make_pw_hash(name, pw) that returns a hashed password
# of the format:
# HASH(pw + salt),salt
# use sha256
def make_pw_hash(self, pw,salt=None):
if salt == None:
salt = self.make_salt();
return hashlib.sha256(pw + salt).hexdigest()+","+ salt
# Validates a user login. Returns user record or None
def validate_login(self, username, password):
user = None
try:
user = self.users.find_one({'_id': username})
except:
print "Unable to query database for user"
if user is None:
print "User not in database"
return None
salt = user['password'].split(',')[1]
if user['password'] != self.make_pw_hash(password, salt):
print "user password is not a match"
return None
# Looks good
return user
# creates a new user in the users collection
def add_user(self, username, password, email):
password_hash = self.make_pw_hash(password)
user = {'_id': username, 'password': password_hash}
if email != "":
user['email'] = email
try:
self.users.insert_one(user)
except pymongo.errors.OperationFailure:
print "oops, mongo error"
return False
except pymongo.errors.DuplicateKeyError as e:
print "oops, username is already taken"
return False
return True
+39
View File
@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<ul>
%for artist in artists:
<li><a href="/comics/artist/{{artist['_id']}}">{{artist['name']}}</a></li>
%end
</ul>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
+43
View File
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<form action="/comics/artist/create" method="POST">
{{errors}}
<h2>Title</h2>
<input type="hidden" name="id", value="{{id}}">
<input type="text" name="name" size="60" value="{{name}}"><br>
<p>
<input type="submit" value="Submit">
</form>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
+44
View File
@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<table border="1">
<tr><td>Title</td><td>Current Order</td><td>Completed</td></tr>
<tr>
%for book in books:
<td><a href="/library/book/{{book['_id']}}">{{book['title']}}</a></td>
<td>{{book['current_order']}}</td>
<td>{{book['completed']}}</td>
%end
</tr>
</table>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
+34
View File
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
+44
View File
@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<table border="1">
<tr><td>Title</td><td>Current Order</td><td>Completed</td></tr>
<tr>
%for comic in comics:
<td><a href="/comics/comic/{{comic['_id']}}">{{comic['title']}}</a></td>
<td>{{comic['current_order']}}</td>
<td>{{comic['completed']}}</td>
%end
</tr>
</table>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
+51
View File
@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<form action="/comics/comic/create" method="POST">
{{errors}}
<h2>Title</h2>
<input type="hidden" name="id", value="{{id}}">
<input type="text" name="title" size="60" value="{{title}}"><br>
<p>
<label>
<input type="checkbox" name="current_order" size="60" value="{{current_order}}">
Current Order
</label>
<label>
<input type="checkbox" name="completed" size="60" value="{{completed}}">
Completed
</label>
<p>
<input type="submit" value="Submit">
</form>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
+28
View File
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/office">HomeOffice</a></li>
<li><a href="/comics">Comics</a></li>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
</details>
</main>
%if (username == None):
<footer><a href="/login">Login</a><p>Ingenieurb&uuml;ro Thomas Peetz</p></footer>
%end
%if (username != None):
<footer><a href="/logout">{{username}}</a><p>Ingenieurb&uuml;ro Thomas Peetz</p></footer>
%end
</body>
</html>
+52
View File
@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<form method="POST">
<table>
<tr>
<td class="label">Username</td>
<td><input type="text" name="username" value="{{username}}"></td>
<td class="error"></td>
</tr>
<tr>
<td class="label">Password</td>
<td><input type="password" name="password" value=""></td>
<td class="error">{{login_error}}</td>
</tr>
</table>
<input type="submit" value="Submit">
</form>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
+32
View File
@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/office">HomeOffice</a></li>
<li><a href="/comics">Comics</a></li>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<h2>Medien</h2>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
+35
View File
@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/office">HomeOffice</a></li>
<ul>
<li><a href="/office/travel">Dienstreise</a></li>
</ul>
<li><a href="/comics">Comics</a></li>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<h2>HomeOffice</h2>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
+39
View File
@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<ul>
%for publisher in publishers:
<li><a href="/comics/publisher/{{publisher['_id']}}">{{publisher['name']}}</a></li>
%end
</ul>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
+42
View File
@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics/comic">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<form action="/comics/publisher/create" method="POST">
{{errors}}
<h2>Title</h2>
<input type="hidden" name="id", value="{{id}}">
<input type="text" name="name" size="60" value="{{name}}"><br>
<p>
<input type="submit" value="Submit">
</form>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
+59
View File
@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<h2>Signup</h2>
<form method="post">
<table>
<tr><td class="label">Username</td>
<td><input type="text" name="username" value="{{username}}"></td>
<td class="error">{{username_error}}</td>
</tr>
<tr>
<td class="label">Password</td>
<td><input type="password" name="password" value=""></td>
<td class="error">{{password_error}}</td>
</tr>
<tr>
<td class="label">Verify Password</td>
<td><input type="password" name="verify" value=""></td>
<td class="error">{{verify_error}}</td>
</tr>
<tr>
<td class="label">Email (optional)</td>
<td><input type="text" name="email" value="{{email}}"></td>
<td class="error">{{email_error}}</td>
</tr>
</table>
<input type="submit">
</form>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
+40
View File
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics/comic">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
<li><a href="/comics/storyarc">StoryArcs</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<ul>
%for storyarc in storyarcs:
<li><a href="/comics/storyarc/{{storyarc['_id']}}">{{storyarc['title']}}</a></li>
%end
</ul>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
+43
View File
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/comics">Comics</a></li>
<ul>
<li><a href="/comics/artist">Artists</a></li>
<li><a href="/comics/publisher">Publishers</a></li>
<li><a href="/comics/storyarc">StoryArcs</a></li>
</ul>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<form action="/comics/storyarc/create" method="POST">
{{errors}}
<h2>Title</h2>
<input type="hidden" name="id", value="{{id}}">
<input type="text" name="title" size="60" value="{{title}}"><br>
<p>
<input type="submit" value="Submit">
</form>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
+32
View File
@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<title>Kontor</title>
<link rel="stylesheet" href="/css/kontor.css">
</head>
<body>
<header>Kontor</header>
<nav><ul>
<li><a href="/">Kontor</a></li>
<li><a href="/office">HomeOffice</a></li>
<li><a href="/comics">Comics</a></li>
<li><a href="/library">B&uuml;cher</a></li>
<li><a href="/medien">Medien</a></li>
<li><a href="/tradingcards">Trading Cards</a></li>
</ul></nav>
<main role="main">
<details>
<h2>Trading Cards</h2>
</details>
</main>
<footer>
%if (username == None):
<a href="/login">Login</a>
%end
%if (username != None):
<a href="/logout">{{username}}</a>
%end
<p>Ingenieurb&uuml;ro Thomas Peetz</p>
</footer>
</body>
</html>
+16
View File
@@ -0,0 +1,16 @@
bin/
include/
lib/
lib64/
lib64
pip-selfcheck.json
<<<<<<< HEAD
<<<<<<< HEAD
*.pyc
kontor/db.sqlite3
.idea/
=======
>>>>>>> initial setup
=======
*.pyc
>>>>>>> ignore compiled scripts
+13
View File
@@ -0,0 +1,13 @@
node {
stage "Checkout"
checkout scm
stage "make migrations"
sh "python kontor/manage.py makemigrations"
stage "execute tests"
<<<<<<< HEAD
sh "python kontor/manage.py test comics library medien tradingcards homeoffice"
=======
sh "python kontor/manage.py test commics library medien tradingcards homeoffice"
>>>>>>> add tests
}
+2
View File
@@ -0,0 +1,2 @@
# Kontor Django
View File
+86
View File
@@ -0,0 +1,86 @@
from django.contrib import admin
<<<<<<< HEAD
<<<<<<< HEAD
=======
>>>>>>> add Comic models
from .models import Publisher
from .models import Artist
from .models import Issue
from .models import StoryArc
from .models import Comic
from .models import TradePaperback
from .models import Volume
<<<<<<< HEAD
# Register your models here.
class IssueInline(admin.StackedInline):
model = Issue
extra = 1
class IssueAdmin(admin.ModelAdmin):
fields = ['comic', 'number', 'is_read', 'in_stock']
class ComicAdmin(admin.ModelAdmin):
fields = ['title', 'publisher', 'writer', 'current_order', 'completed']
inlines = [IssueInline]
class ComicInline(admin.StackedInline):
model = Comic
extra = 1
class PublisherAdmin(admin.ModelAdmin):
inlines = [ComicInline]
admin.site.register(Publisher, PublisherAdmin)
admin.site.register(Artist)
admin.site.register(Issue, IssueAdmin)
admin.site.register(Volume)
admin.site.register(StoryArc)
admin.site.register(Comic, ComicAdmin)
admin.site.register(TradePaperback)
=======
# Register your models here.
>>>>>>> initial setup
=======
# Register your models here.
class IssueInline(admin.StackedInline):
model = Issue
extra = 1
class IssueAdmin(admin.ModelAdmin):
fields = ['comic', 'number', 'is_read', 'in_stock']
class ComicAdmin(admin.ModelAdmin):
fields = ['title', 'publisher', 'writer', 'artist', 'current_order', 'completed']
inlines = [IssueInline]
class ComicInline(admin.StackedInline):
model = Comic
extra = 1
class PublisherAdmin(admin.ModelAdmin):
inlines = [ComicInline]
admin.site.register(Publisher, PublisherAdmin)
admin.site.register(Artist)
admin.site.register(Issue, IssueAdmin)
admin.site.register(Volume)
admin.site.register(StoryArc)
admin.site.register(Comic, ComicAdmin)
admin.site.register(TradePaperback)
>>>>>>> add Comic models
+5
View File
@@ -0,0 +1,5 @@
from django.apps import AppConfig
class ComicsConfig(AppConfig):
name = 'comics'
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,105 @@
# -*- coding: utf-8 -*-
<<<<<<< HEAD
# Generated by Django 1.9.6 on 2016-06-13 15:33
=======
# Generated by Django 1.9.5 on 2016-04-10 20:23
>>>>>>> ignore compiled scripts
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Artist',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
],
),
migrations.CreateModel(
name='Comic',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=40)),
('current_order', models.BooleanField()),
('completed', models.BooleanField()),
<<<<<<< HEAD
('artist', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='artist', to='comics.Artist')),
=======
>>>>>>> ignore compiled scripts
],
),
migrations.CreateModel(
name='Issue',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('number', models.CharField(max_length=4)),
('is_read', models.BooleanField(default=False)),
('in_stock', models.BooleanField(default=False)),
('comic', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='comics.Comic')),
],
),
migrations.CreateModel(
name='Publisher',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
],
),
migrations.CreateModel(
name='StoryArc',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('comic', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='comics.Comic')),
],
),
migrations.CreateModel(
name='TradePaperback',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=40)),
('issue_start', models.IntegerField()),
('issue_end', models.IntegerField()),
('comic', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='comics.Comic')),
],
),
migrations.CreateModel(
name='Volume',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=40)),
('comic', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='comics.Comic')),
],
),
migrations.AddField(
<<<<<<< HEAD
model_name='issue',
name='volume',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='comics.Volume'),
),
migrations.AddField(
=======
>>>>>>> ignore compiled scripts
model_name='comic',
name='publisher',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='comics.Publisher'),
),
<<<<<<< HEAD
migrations.AddField(
model_name='comic',
name='writer',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='writer', to='comics.Artist'),
),
=======
>>>>>>> ignore compiled scripts
]
@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-06-20 14:05
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('comics', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='comic',
name='artist',
field=models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='artist', to='comics.Artist'),
),
migrations.AlterField(
model_name='comic',
name='writer',
field=models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='writer', to='comics.Artist'),
),
]
@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-06-20 14:10
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('comics', '0002_auto_20160620_1405'),
]
operations = [
migrations.AlterField(
model_name='comic',
name='artist',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='artist', to='comics.Artist'),
),
migrations.AlterField(
model_name='comic',
name='writer',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='writer', to='comics.Artist'),
),
]
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-06-22 11:22
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('comics', '0003_auto_20160620_1410'),
]
operations = [
migrations.AlterField(
model_name='issue',
name='volume',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comics.Volume'),
),
]
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-06-22 11:46
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('comics', '0004_auto_20160622_1122'),
]
operations = [
migrations.AlterField(
model_name='comic',
name='title',
field=models.CharField(max_length=60),
),
]
View File
+101
View File
@@ -0,0 +1,101 @@
from __future__ import unicode_literals
from django.db import models
# Create your models here.
<<<<<<< HEAD
<<<<<<< HEAD
=======
>>>>>>> add Comic models
class Publisher(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Artist(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Comic(models.Model):
<<<<<<< HEAD
title = models.CharField(max_length=60)
publisher = models.ForeignKey(Publisher)
current_order = models.BooleanField()
completed = models.BooleanField()
writer = models.ForeignKey(Artist, blank=True, null=True, related_name='writer')
artist = models.ForeignKey(Artist, blank=True, null=True, related_name='artist')
=======
title = models.CharField(max_length=40)
publisher = models.ForeignKey(Publisher)
current_order = models.BooleanField()
completed = models.BooleanField()
<<<<<<< HEAD
>>>>>>> add Comic models
=======
writer = models.ForeignKey(Artist, null=True, related_name='writer')
artist = models.ForeignKey(Artist, null=True, related_name='artist')
>>>>>>> backup
def __str__(self):
return self.title
class Volume(models.Model):
name = models.CharField(max_length=40)
comic = models.ForeignKey(Comic)
def __str__(self):
return self.comic.title + ' ' + self.name
class Issue(models.Model):
number = models.CharField(max_length=4)
is_read = models.BooleanField(default=False)
in_stock = models.BooleanField(default=False)
comic = models.ForeignKey(Comic, null=True)
<<<<<<< HEAD
<<<<<<< HEAD
volume = models.ForeignKey(Volume, blank=True, null=True)
=======
>>>>>>> add Comic models
=======
volume = models.ForeignKey(Volume, null=True)
>>>>>>> backup
def __str__(self):
return self.comic.title + ' #' + self.number
class StoryArc(models.Model):
name = models.CharField(max_length=100)
comic = models.ForeignKey(Comic, null=True)
def __str__(self):
return self.name
class TradePaperback(models.Model):
name = models.CharField(max_length=40)
comic = models.ForeignKey(Comic, null=True)
issue_start = models.IntegerField()
issue_end = models.IntegerField()
def __str__(self):
<<<<<<< HEAD
return self.comic.title + ' TP ' + self.name
<<<<<<< HEAD
=======
>>>>>>> initial setup
=======
>>>>>>> add Comic models
=======
return self.comic.title + ' TP ' + self.name
>>>>>>> backup
@@ -0,0 +1,6 @@
li a {
color: green;
}
td a {
color: green;
}
+28
View File
@@ -0,0 +1,28 @@
<<<<<<< HEAD
{% extends "kontor/base.html" %}
{% block title %}Comic Details{% endblock %}
{% block content %}
=======
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Comic Details</title>
</head>
<body>
>>>>>>> backup
<table>
<thead><td>Name</td><td>current Order</td><td>completed</td></thead>
<tr>
<td>{{ comic.title }}</td>
<td>{{ comic.current_order }}</td>
<td>{{ comic.completed }}</td>
</tr>
</table>
<<<<<<< HEAD
{% endblock %}
=======
</body>
</html>
>>>>>>> backup
+34
View File
@@ -0,0 +1,34 @@
<<<<<<< HEAD
{% extends "kontor/base.html" %}
{% block title %}Comic Overview{% endblock %}
{% block content %}
=======
{% load staticfiles %}
<link rel="stylesheet" type="text/css" href="{% static 'comics/style.css' %}" />
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Comic Overview</title>
</head>
<body>
>>>>>>> backup
<table>
<thead><td>Name</td><td>current Order</td><td>completed</td></thead>
{% for comic in comic_list %}
<tr>
<td><a href="{% url 'comics:detail' comic.id %}">{{ comic.title }}</a></td>
<td>{{ comic.current_order }}</td>
<td>{{ comic.completed }}</td>
</tr>
{% endfor %}
</table>
<<<<<<< HEAD
{% endblock %}
=======
</body>
</html>
>>>>>>> backup
+8
View File
@@ -0,0 +1,8 @@
from django.test import TestCase
# Create your tests here.
class ComicsViewsTestCase(TestCase):
def test_index(self):
resp = self.client.get('/comics/')
self.assertEqual(resp.status_code, 200)
+10
View File
@@ -0,0 +1,10 @@
from django.conf.urls import url
from . import views
app_name = 'comics'
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'),
]
+51
View File
@@ -0,0 +1,51 @@
<<<<<<< HEAD
from django.shortcuts import render
<<<<<<< HEAD
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.views import generic
from .models import Comic
# Create your views here.
class IndexView(generic.ListView):
template_name = 'comics/index.html'
context_object_name = 'comic_list'
def get_queryset(self):
"""Return the list of comics."""
return Comic.objects.all()
class DetailView(generic.DetailView):
model = Comic
template_name = 'comics/detail.html'
=======
=======
from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.views import generic
>>>>>>> backup
from .models import Comic
# Create your views here.
<<<<<<< HEAD
>>>>>>> initial setup
=======
class IndexView(generic.ListView):
template_name = 'comics/index.html'
context_object_name = 'comic_list'
def get_queryset(self):
"""Return the list of comics."""
return Comic.objects.all()
class DetailView(generic.DetailView):
model = Comic
template_name = 'comics/detail.html'
>>>>>>> backup
View File
+3
View File
@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.
+7
View File
@@ -0,0 +1,7 @@
from __future__ import unicode_literals
from django.apps import AppConfig
class HomeOfficeConfig(AppConfig):
name = 'homeoffice'
@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-06-20 16:58
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Expense',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
),
migrations.CreateModel(
name='Travel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
),
]
View File
+10
View File
@@ -0,0 +1,10 @@
from __future__ import unicode_literals
from django.db import models
# Create your models here.
class Travel(models.Model):
pass
class Expense(models.Model):
pass
+2
View File
@@ -0,0 +1,2 @@
{% extends "kontor/base.html" %}
{% block title %}HomeOffice{% endblock %}
+3
View File
@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.
+8
View File
@@ -0,0 +1,8 @@
from django.conf.urls import url
from . import views
app_name = 'homeoffice'
urlpatterns = [
url(r'^$', views.index, name='index'),
]
+5
View File
@@ -0,0 +1,5 @@
from django.shortcuts import render
# Create your views here.
def index(request):
return render(request, 'homeoffice/index.html')
View File
+166
View File
@@ -0,0 +1,166 @@
"""
Django settings for kontor project.
<<<<<<< HEAD
<<<<<<< HEAD
Generated by 'django-admin startproject' using Django 1.9.6.
=======
Generated by 'django-admin startproject' using Django 1.9.5.
>>>>>>> initial setup
=======
>>>>>>> backup
For more information on this file, see
https://docs.djangoproject.com/en/1.6/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.6/ref/settings/
"""
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
<<<<<<< HEAD
<<<<<<< HEAD
SECRET_KEY = '8s-(!r%0yu)b(hdkes_v#g8kp7)0ma851j+&43cds#duk!s-_c'
=======
SECRET_KEY = 'c)&86w07k)091bgi1llt+aol5$8in8g=n#+iba4784cdw$#h)^'
>>>>>>> initial setup
=======
SECRET_KEY = 'i5qx(%mex)4ovh#y4m94b1(3xiw4%8+rx-!kpw9v4q*@0v6pd2'
>>>>>>> backup
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
TEMPLATE_DEBUG = True
ALLOWED_HOSTS = []
# Application definition
<<<<<<< HEAD
INSTALLED_APPS = [
<<<<<<< HEAD
<<<<<<< HEAD
'comics.apps.ComicsConfig',
'library.apps.LibraryConfig',
'tysc.apps.TyscConfig',
'tradingcards.apps.TradingCardsConfig',
'homeoffice.apps.HomeOfficeConfig',
'medien.apps.MedienConfig',
=======
>>>>>>> initial setup
=======
'comics.apps.ComicsConfig',
>>>>>>> add admin site for Comics
=======
INSTALLED_APPS = (
'comics.apps.ComicsConfig',
'tysc.apps.TyscConfig',
'library.apps.LibraryConfig',
>>>>>>> backup
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
)
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
ROOT_URLCONF = 'kontor.urls'
<<<<<<< HEAD
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
<<<<<<< HEAD
'DIRS': [os.path.join(BASE_DIR, 'templates')],
=======
'DIRS': [],
>>>>>>> initial setup
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
<<<<<<< HEAD
'django.template.context_processors.i18n',
'django.template.context_processors.media',
'django.template.context_processors.static',
'django.template.context_processors.tz',
=======
>>>>>>> initial setup
],
},
},
]
=======
>>>>>>> backup
WSGI_APPLICATION = 'kontor.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.6/ref/settings/#databases
DATABASES = {
'default': {
# 'ENGINE': 'django.db.backends.dummy',
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
#from mongoengine import connect
#connect('kontor')
#SESSION_ENGINE = 'mongoengine.django.sessions'
#SESSION_SERIALIZER = 'mongoengine.django.sessions.BSONSerializer'
# Internationalization
# https://docs.djangoproject.com/en/1.6/topics/i18n/
<<<<<<< HEAD
LANGUAGE_CODE = 'de-de'
=======
LANGUAGE_CODE = 'en-us'
>>>>>>> initial setup
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.6/howto/static-files/
STATIC_URL = '/static/'
<<<<<<< HEAD
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
]
=======
>>>>>>> initial setup
+61
View File
@@ -0,0 +1,61 @@
from django.conf.urls import patterns, include, url
<<<<<<< HEAD
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/1.9/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
<<<<<<< HEAD
from django.conf.urls import url, include
from django.contrib import admin
from django.contrib.auth import views as auth_views
from . import views
admin.autodiscover()
urlpatterns = [
#url(r'^admin/', admin.site.urls),
url('^', include('django.contrib.auth.urls')),
url(r'^$', views.index, name='index'),
#url(r'^accounts/login/$', auth_views.login),
url(r'^accounts/login/$', auth_views.login, {'template_name': 'kontor/login.html'}),
url(r'^accounts/logout/$', auth_views.logout ),
url(r'^admin/', include(admin.site.urls)),
url(r'^comics/', include('comics.urls')),
url(r'^office/', include('homeoffice.urls')),
url(r'^tradingcards/', include('tradingcards.urls')),
url(r'^tysc/', include('tysc.urls')),
url(r'^library/', include('library.urls')),
url(r'^medien/', include('medien.urls')),
=======
from django.conf.urls import url
=======
>>>>>>> backup
from django.contrib import admin
admin.autodiscover()
<<<<<<< HEAD
urlpatterns = [
url(r'^admin/', admin.site.urls),
>>>>>>> initial setup
]
=======
urlpatterns = patterns('',
# Examples:
#url(r'^$', 'kontor.views.home', name='home'),
# url(r'^blog/', include('blog.urls')),
url(r'^comics/', include('comics.urls')),
url(r'^tysc/', include('tysc.urls')),
url(r'^admin/', include(admin.site.urls)),
)
>>>>>>> backup
+14
View File
@@ -0,0 +1,14 @@
from django.shortcuts import render
from django.http import HttpResponse
def index(request):
<<<<<<< HEAD
return render(request, 'kontor/index.html')
=======
return HttpResponse('You are in the Kontor application')
>>>>>>> backup
def home(request):
return HttpResponse('You are in the Kontor application')
+14
View File
@@ -0,0 +1,14 @@
"""
WSGI config for kontor project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/
"""
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "kontor.settings")
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()

Some files were not shown because too many files have changed in this diff Show More