Initial commit
This commit is contained in:
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)
|
||||
Reference in New Issue
Block a user