mirror of
https://github.com/minetest/contentdb.git
synced 2025-01-05 12:47:29 +01:00
Initial commit
This commit is contained in:
commit
366a2302d0
172
.gitignore
vendored
Normal file
172
.gitignore
vendored
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
config.cfg
|
||||||
|
*.sqlite
|
||||||
|
main.css
|
||||||
|
|
||||||
|
|
||||||
|
# Created by https://www.gitignore.io/api/linux,macos,python,windows
|
||||||
|
|
||||||
|
### Linux ###
|
||||||
|
*~
|
||||||
|
|
||||||
|
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||||
|
.fuse_hidden*
|
||||||
|
|
||||||
|
# KDE directory preferences
|
||||||
|
.directory
|
||||||
|
|
||||||
|
# Linux trash folder which might appear on any partition or disk
|
||||||
|
.Trash-*
|
||||||
|
|
||||||
|
# .nfs files are created when an open file is removed but is still being accessed
|
||||||
|
.nfs*
|
||||||
|
|
||||||
|
### macOS ###
|
||||||
|
*.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Icon must end with two \r
|
||||||
|
Icon
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear in the root of a volume
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|
||||||
|
### Python ###
|
||||||
|
# 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/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# 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/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
.pytest_cache/
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
.hypothesis/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# celery beat schedule file
|
||||||
|
celerybeat-schedule.*
|
||||||
|
|
||||||
|
# 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/
|
||||||
|
|
||||||
|
### Windows ###
|
||||||
|
# Windows thumbnail cache files
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
ehthumbs_vista.db
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
Desktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Windows Installer files
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# Windows shortcuts
|
||||||
|
*.lnk
|
||||||
|
|
||||||
|
|
||||||
|
# End of https://www.gitignore.io/api/linux,macos,python,windows
|
7
app/__init__.py
Normal file
7
app/__init__.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from flask import *
|
||||||
|
from flask_user import *
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config.from_pyfile('../config.cfg')
|
||||||
|
|
||||||
|
import models, views
|
72
app/models.py
Normal file
72
app/models.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
from flask import Flask, url_for
|
||||||
|
from flask.ext.sqlalchemy import SQLAlchemy
|
||||||
|
from app import app
|
||||||
|
from datetime import datetime
|
||||||
|
from sqlalchemy.orm import validates
|
||||||
|
from flask_user import login_required, UserManager, UserMixin, SQLAlchemyAdapter
|
||||||
|
|
||||||
|
# Initialise database
|
||||||
|
db = SQLAlchemy(app)
|
||||||
|
|
||||||
|
def title_to_url(title):
|
||||||
|
return title.lower().replace(" ", "_")
|
||||||
|
|
||||||
|
def url_to_title(url):
|
||||||
|
return url.replace("_", " ")
|
||||||
|
|
||||||
|
class User(db.Model, UserMixin):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
|
||||||
|
# User authentication information
|
||||||
|
username = db.Column(db.String(50), nullable=False, unique=True)
|
||||||
|
password = db.Column(db.String(255), nullable=False, server_default='')
|
||||||
|
reset_password_token = db.Column(db.String(100), nullable=False, server_default='')
|
||||||
|
|
||||||
|
# User email information
|
||||||
|
email = db.Column(db.String(255), nullable=True, unique=True)
|
||||||
|
confirmed_at = db.Column(db.DateTime())
|
||||||
|
|
||||||
|
# User information
|
||||||
|
active = db.Column('is_active', db.Boolean, nullable=False, server_default='0')
|
||||||
|
display_name = db.Column(db.String(100), nullable=False, server_default='')
|
||||||
|
|
||||||
|
# Content
|
||||||
|
mods = db.relationship('Mod', backref='author', lazy='dynamic')
|
||||||
|
|
||||||
|
def __init__(self, username):
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
self.username = username
|
||||||
|
self.confirmed_at = datetime.datetime.now() - datetime.timedelta(days=6000)
|
||||||
|
|
||||||
|
def isClaimed(self):
|
||||||
|
return self.password is not None and self.password != ""
|
||||||
|
|
||||||
|
class Role(db.Model):
|
||||||
|
id = db.Column(db.Integer(), primary_key=True)
|
||||||
|
name = db.Column(db.String(50), unique=True)
|
||||||
|
description = db.Column(db.String(255))
|
||||||
|
|
||||||
|
class UserRoles(db.Model):
|
||||||
|
id = db.Column(db.Integer(), primary_key=True)
|
||||||
|
user_id = db.Column(db.Integer(), db.ForeignKey('user.id', ondelete='CASCADE'))
|
||||||
|
role_id = db.Column(db.Integer(), db.ForeignKey('role.id', ondelete='CASCADE'))
|
||||||
|
|
||||||
|
class Mod(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
|
||||||
|
# Basic details
|
||||||
|
author_id = db.Column(db.Integer, db.ForeignKey('user.id'))
|
||||||
|
name = db.Column(db.String(100), nullable=False)
|
||||||
|
title = db.Column(db.String(100), nullable=False)
|
||||||
|
desc = db.Column(db.Text, nullable=True)
|
||||||
|
|
||||||
|
# Downloads
|
||||||
|
repo = db.Column(db.String(200), nullable=True)
|
||||||
|
website = db.Column(db.String(200), nullable=True)
|
||||||
|
issueTracker = db.Column(db.String(200), nullable=True)
|
||||||
|
forums = db.Column(db.String(200), nullable=False)
|
||||||
|
|
||||||
|
# Setup Flask-User
|
||||||
|
db_adapter = SQLAlchemyAdapter(db, User) # Register the User model
|
||||||
|
user_manager = UserManager(db_adapter, app) # Initialize Flask-User
|
BIN
app/static/screenshot.png
Normal file
BIN
app/static/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 224 KiB |
245
app/static/style.css
Normal file
245
app/static/style.css
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
html, body {
|
||||||
|
font-family: "Arial", sans-serif;
|
||||||
|
background: #222;
|
||||||
|
color: #ddd;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
h2, h3 {
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #0be;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #0df;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Containers */
|
||||||
|
|
||||||
|
.box {
|
||||||
|
border-radius: 5px;
|
||||||
|
margin: 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box_grey {
|
||||||
|
padding: 10px;
|
||||||
|
background: #333;
|
||||||
|
border: 1px solid #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ul_boxes {
|
||||||
|
display: block;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ul_boxes > li {
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box_link {
|
||||||
|
display: block;
|
||||||
|
color: #ddd;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box_link:hover{
|
||||||
|
background: #3a3a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
buttonset
|
||||||
|
*/
|
||||||
|
|
||||||
|
.buttonset, .buttonset li {
|
||||||
|
display: block;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonset {
|
||||||
|
margin: 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonset li a {
|
||||||
|
text-align: center;
|
||||||
|
color: #ddd;
|
||||||
|
text-decoration: none;
|
||||||
|
margin: 5px 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonset li a:hover {
|
||||||
|
background: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn_green {
|
||||||
|
background: #363 !important;
|
||||||
|
border: 1px solid #473;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn_green:hover {
|
||||||
|
background: #474 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alerts */
|
||||||
|
|
||||||
|
#alerts {
|
||||||
|
list-style: none;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 15px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#alerts .alert {
|
||||||
|
margin: 5px 0;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
#alerts .close {
|
||||||
|
float: right;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#alerts .close:hover {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-error {
|
||||||
|
background: #933;
|
||||||
|
border: 1px solid #c44;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-warning {
|
||||||
|
background: #963;
|
||||||
|
border: 1px solid #c96;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nav */
|
||||||
|
|
||||||
|
nav, main, #alerts {
|
||||||
|
width: 90%;
|
||||||
|
max-width: 960px;
|
||||||
|
margin: auto;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
margin: 15px auto 5px auto;
|
||||||
|
list-style: none;
|
||||||
|
background: #333;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav .navbar-nav {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav .navbar-right {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ul {
|
||||||
|
margin: 0 auto 0 auto;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav li {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav li a {
|
||||||
|
color: #ddd;
|
||||||
|
margin: 0;
|
||||||
|
padding: 10px 20px;
|
||||||
|
display: block;
|
||||||
|
border-left: 1px solid #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a:hover {
|
||||||
|
color: #eee;
|
||||||
|
background: #444;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
|
||||||
|
footer {
|
||||||
|
width: 80%;
|
||||||
|
max-width: 860px;
|
||||||
|
margin: auto;
|
||||||
|
padding: 50px 0 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Mod */
|
||||||
|
|
||||||
|
.box_img {
|
||||||
|
position: relative;
|
||||||
|
background-position: center;
|
||||||
|
background-size: cover;
|
||||||
|
background-image: url("screenshot.png");
|
||||||
|
min-height: 220px;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box_img > h2 {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 15px;
|
||||||
|
left: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar_container {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar_container .right, .sidebar_container .left{
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
top: 10px;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar_container .right {
|
||||||
|
right: 0;
|
||||||
|
width: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar_container .left {
|
||||||
|
right: 295px;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar_container .right > *:first-child, .sidebar_container .left > *:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
74
app/templates/base.html
Normal file
74
app/templates/base.html
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>{% block title %}title{% endblock %} - {{ config.USER_APP_NAME }}</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/style.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<nav>
|
||||||
|
<ul class="nav navbar-nav">
|
||||||
|
<li><a href="/">{{ config.USER_APP_NAME }}</a></li>
|
||||||
|
{% for item in current_menu.children recursive %}
|
||||||
|
<li{% if item.children %} class="dropdown"{% endif %}>
|
||||||
|
<a href="{{ item.url }}"
|
||||||
|
{% if item.children %}
|
||||||
|
class="dropdown-toggle"
|
||||||
|
data-toggle="dropdown"
|
||||||
|
role="button"
|
||||||
|
aria-expanded="false"
|
||||||
|
{% endif %}>
|
||||||
|
{{ item.text }}
|
||||||
|
{% if item.children %}
|
||||||
|
<span class="caret"></span>
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
{% if item.children %}
|
||||||
|
<ul class="dropdown-menu" role="menu">
|
||||||
|
{{ loop(item.children) }}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
<ul class="nav navbar-nav navbar-right">
|
||||||
|
{% if current_user.is_authenticated %}
|
||||||
|
<li><a href="{{ url_for('user_profile_page') }}">{{ current_user.first_name or current_user.username }}</a></li>
|
||||||
|
<li><a href="{{ url_for('user.logout') }}">Sign out</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li><a href="{{ url_for('user.login') }}">Sign in</a></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
<div style="clear:both;"></div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
|
{% block flash_messages %}
|
||||||
|
{%- with messages = get_flashed_messages(with_categories=true) -%}
|
||||||
|
{% if messages %}
|
||||||
|
<ul id="alerts">
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<li class="box box_grey alert alert-{{category}}">
|
||||||
|
<span class="icon_message"></span>
|
||||||
|
|
||||||
|
{{ message|safe }}
|
||||||
|
|
||||||
|
<div style="clear: both;"></div>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
{%- endwith %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block container %}
|
||||||
|
<main>
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
</main>
|
||||||
|
{% endblock %}
|
||||||
|
</html>
|
76
app/templates/flask_user/login.html
Normal file
76
app/templates/flask_user/login.html
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Sign in
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="sidebar_container">
|
||||||
|
<div class="left box box_grey">
|
||||||
|
{% from "flask_user/_macros.html" import render_field, render_checkbox_field, render_submit_field %}
|
||||||
|
<h2>{%trans%}Sign in{%endtrans%}</h2>
|
||||||
|
|
||||||
|
<form action="" method="POST" class="form" role="form">
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
|
||||||
|
{# Username or Email field #}
|
||||||
|
{% set field = form.username if user_manager.enable_username else form.email %}
|
||||||
|
<div class="form-group {% if field.errors %}has-error{% endif %}">
|
||||||
|
{# Label on left, "New here? Register." on right #}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-6">
|
||||||
|
<label for="{{ field.id }}" class="control-label">{{ field.label.text }}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ field(class_='form-control', tabindex=110) }}
|
||||||
|
{% if field.errors %}
|
||||||
|
{% for e in field.errors %}
|
||||||
|
<p class="help-block">{{ e }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Password field #}
|
||||||
|
{% set field = form.password %}
|
||||||
|
<div class="form-group {% if field.errors %}has-error{% endif %}">
|
||||||
|
{# Label on left, "Forgot your Password?" on right #}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-6">
|
||||||
|
<label for="{{ field.id }}" class="control-label">{{ field.label.text }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 text-right">
|
||||||
|
{% if user_manager.enable_forgot_password %}
|
||||||
|
<a href="{{ url_for('user.forgot_password') }}" tabindex='195'>
|
||||||
|
{%trans%}Forgot your Password?{%endtrans%}</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ field(class_='form-control', tabindex=120) }}
|
||||||
|
{% if field.errors %}
|
||||||
|
{% for e in field.errors %}
|
||||||
|
<p class="help-block">{{ e }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Remember me #}
|
||||||
|
{% if user_manager.enable_remember_me %}
|
||||||
|
{{ render_checkbox_field(login_form.remember_me, tabindex=130) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Submit button #}
|
||||||
|
{{ render_submit_field(form.submit, tabindex=180) }}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="right">
|
||||||
|
<aside class="box box_grey">
|
||||||
|
<h2>New here?</h2>
|
||||||
|
|
||||||
|
{% if user_manager.enable_register and not user_manager.require_invitation %}
|
||||||
|
<a href="">{%trans%}Create an account{%endtrans%}</a>
|
||||||
|
{% endif %}
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
10
app/templates/flask_user/public_base.html
Normal file
10
app/templates/flask_user/public_base.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block container %}
|
||||||
|
<main>
|
||||||
|
<div class="box box_grey">
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{% endblock %}
|
35
app/templates/index.html
Normal file
35
app/templates/index.html
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Dashboard
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="box box_grey">
|
||||||
|
<h2>{{ self.title() }}</h2>
|
||||||
|
|
||||||
|
{% if current_user.is_authenticated %}
|
||||||
|
<p>
|
||||||
|
Hello user!
|
||||||
|
</p>
|
||||||
|
{% else %}
|
||||||
|
<p>
|
||||||
|
Please login!
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="2box">
|
||||||
|
<div class="box box_grey">
|
||||||
|
<h2>Top Mods</h2>
|
||||||
|
</div>
|
||||||
|
<div class="box box_grey">
|
||||||
|
<h2>Statistics</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Total mods: 543</li>
|
||||||
|
<li>Missing mods: 1020</li>
|
||||||
|
<li>Downloads/day: 200</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
74
app/views/__init__.py
Normal file
74
app/views/__init__.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
from app import app
|
||||||
|
from flask import *
|
||||||
|
from flask_user import *
|
||||||
|
from flask_login import login_user, logout_user
|
||||||
|
from app.models import *
|
||||||
|
from flask.ext import menu, markdown
|
||||||
|
from sqlalchemy import func
|
||||||
|
from werkzeug.contrib.cache import SimpleCache
|
||||||
|
cache = SimpleCache()
|
||||||
|
|
||||||
|
menu.Menu(app=app)
|
||||||
|
markdown.Markdown(app, extensions=['fenced_code'])
|
||||||
|
|
||||||
|
# TODO: remove on production!
|
||||||
|
@app.route('/static/<path:path>')
|
||||||
|
def send_static(path):
|
||||||
|
return send_from_directory('static', path)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
@menu.register_menu(app, '.', 'Home')
|
||||||
|
def home_page():
|
||||||
|
return render_template('index.html')
|
||||||
|
|
||||||
|
# Define the User registration form
|
||||||
|
# It augments the Flask-User RegisterForm with additional fields
|
||||||
|
from flask_user.forms import RegisterForm
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import StringField, SubmitField, validators
|
||||||
|
class MyRegisterForm(RegisterForm):
|
||||||
|
first_name = StringField('First name', validators=[
|
||||||
|
validators.DataRequired('First name is required')])
|
||||||
|
last_name = StringField('Last name', validators=[
|
||||||
|
validators.DataRequired('Last name is required')])
|
||||||
|
|
||||||
|
# Define the User profile form
|
||||||
|
class UserProfileForm(FlaskForm):
|
||||||
|
first_name = StringField('First name', validators=[
|
||||||
|
validators.DataRequired('First name is required')])
|
||||||
|
last_name = StringField('Last name', validators=[
|
||||||
|
validators.DataRequired('Last name is required')])
|
||||||
|
submit = SubmitField('Save')
|
||||||
|
|
||||||
|
@app.route('/user/', methods=['GET', 'POST'])
|
||||||
|
@app.route('/user/<username>/', methods=['GET'])
|
||||||
|
def user_profile_page(username=None):
|
||||||
|
user = None
|
||||||
|
form = None
|
||||||
|
if username is None:
|
||||||
|
if not current_user.is_authenticated:
|
||||||
|
return current_app.login_manager.unauthorized()
|
||||||
|
user = current_user
|
||||||
|
else:
|
||||||
|
user = User.query.filter_by(username=username).first()
|
||||||
|
if not user:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
if user == current_user:
|
||||||
|
# Initialize form
|
||||||
|
form = UserProfileForm(request.form, current_user)
|
||||||
|
|
||||||
|
# Process valid POST
|
||||||
|
if request.method=='POST' and form.validate():
|
||||||
|
# Copy form fields to user_profile fields
|
||||||
|
form.populate_obj(current_user)
|
||||||
|
|
||||||
|
# Save user_profile
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# Redirect to home page
|
||||||
|
return redirect(url_for('home_page'))
|
||||||
|
|
||||||
|
# Process GET or invalid POST
|
||||||
|
return render_template('users/user_profile_page.html',
|
||||||
|
user=user, form=form)
|
6
config.example.cfg
Normal file
6
config.example.cfg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
USER_APP_NAME="Content DB"
|
||||||
|
|
||||||
|
SECRET_KEY=""
|
||||||
|
WTF_CSRF_SECRET_KEY=""
|
||||||
|
|
||||||
|
SQLALCHEMY_DATABASE_URI = "sqlite:///../db.sqlite"
|
5
requirements.txt
Normal file
5
requirements.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
Flask>=0.10.1
|
||||||
|
Flask-SQLAlchemy>=2.1
|
||||||
|
Flask-User>=0.6.9
|
||||||
|
Flask-Menu>=0.5.0
|
||||||
|
Flask-Markdown>=0.3
|
3
rundebug.py
Normal file
3
rundebug.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from app import app
|
||||||
|
|
||||||
|
app.run(host='0.0.0.0', port=5000, debug=True)
|
17
setup.py
Normal file
17
setup.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import os, datetime
|
||||||
|
|
||||||
|
delete_db = False
|
||||||
|
|
||||||
|
if delete_db and os.path.isfile("app/data.sqlite"):
|
||||||
|
os.remove("app/data.sqlite")
|
||||||
|
|
||||||
|
if not os.path.isfile("app/data.sqlite"):
|
||||||
|
from app import models
|
||||||
|
|
||||||
|
print("Creating database tables...")
|
||||||
|
models.db.create_all()
|
||||||
|
|
||||||
|
print("Filling database...")
|
||||||
|
models.db.session.commit()
|
||||||
|
else:
|
||||||
|
print("Database already exists")
|
Loading…
Reference in New Issue
Block a user