Create app.py
This commit is contained in:
238
app.py
Normal file
238
app.py
Normal file
@@ -0,0 +1,238 @@
|
||||
from flask import Flask, render_template, request, redirect, url_for, flash, abort, make_response, session
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
import datetime
|
||||
import json
|
||||
import random
|
||||
from argon2 import PasswordHasher
|
||||
from argon2.exceptions import VerifyMismatchError
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///quotes.db'
|
||||
app.config['SECRET_KEY'] = 'your_secret_key' # Use environment variable in production
|
||||
db = SQLAlchemy(app)
|
||||
|
||||
# Initialize Argon2 password hasher
|
||||
ph = PasswordHasher()
|
||||
|
||||
# Hardcoded admin credentials (hashed password using Argon2)
|
||||
ADMIN_CREDENTIALS = {
|
||||
'username': 'admin',
|
||||
# Replace this with the hashed password generated by Argon2
|
||||
'password': '$argon2i$v=19$m=65536,t=4,p=1$cWZDc1pQaUJLTUJoaVI4cw$kn8XKz6AEZi8ebXfyyZuzommSypliVFrsGqzOyUEIHA' # Example hash
|
||||
}
|
||||
|
||||
# Define the Quote model
|
||||
class Quote(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
text = db.Column(db.Text, nullable=False)
|
||||
votes = db.Column(db.Integer, default=0)
|
||||
date = db.Column(db.DateTime, default=datetime.datetime.utcnow)
|
||||
status = db.Column(db.Integer, default=0) # 0 = pending, 1 = approved, 2 = rejected
|
||||
|
||||
# Home route to display quotes
|
||||
# Home route to display quotes
|
||||
@app.route('/')
|
||||
def index():
|
||||
page = request.args.get('page', 1, type=int)
|
||||
quotes = Quote.query.filter_by(status=1).order_by(Quote.date.desc()).paginate(page=page, per_page=5)
|
||||
|
||||
# Get the count of approved and pending quotes
|
||||
approved_count = Quote.query.filter_by(status=1).count()
|
||||
pending_count = Quote.query.filter_by(status=0).count()
|
||||
|
||||
return render_template('index.html', quotes=quotes, approved_count=approved_count, pending_count=pending_count)
|
||||
|
||||
# Separate route for submitting quotes
|
||||
@app.route('/submit', methods=['GET', 'POST'])
|
||||
def submit():
|
||||
if request.method == 'POST':
|
||||
quote_text = request.form.get('quote')
|
||||
if not quote_text:
|
||||
flash("Quote cannot be empty.", 'error')
|
||||
return redirect(url_for('submit'))
|
||||
|
||||
new_quote = Quote(text=quote_text)
|
||||
try:
|
||||
db.session.add(new_quote)
|
||||
db.session.commit()
|
||||
flash("Quote submitted successfully!", 'success')
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
flash("Error submitting quote: {}".format(e), 'error')
|
||||
|
||||
return redirect(url_for('index'))
|
||||
|
||||
# Get the count of approved and pending quotes
|
||||
approved_count = Quote.query.filter_by(status=1).count()
|
||||
pending_count = Quote.query.filter_by(status=0).count()
|
||||
|
||||
return render_template('submit.html', approved_count=approved_count, pending_count=pending_count)
|
||||
|
||||
|
||||
# Route to handle voting (upvote/downvote)
|
||||
@app.route('/vote/<int:id>/<action>')
|
||||
def vote(id, action):
|
||||
quote = Quote.query.get_or_404(id)
|
||||
|
||||
# Check if the user has already voted on this quote
|
||||
vote_cookie = request.cookies.get('votes')
|
||||
if vote_cookie:
|
||||
vote_data = json.loads(vote_cookie)
|
||||
else:
|
||||
vote_data = {}
|
||||
|
||||
# If the user has already voted on this quote, check if they're trying to undo the vote
|
||||
if str(id) in vote_data:
|
||||
previous_action = vote_data[str(id)]
|
||||
|
||||
if previous_action == action:
|
||||
# User is trying to undo their vote
|
||||
if action == 'upvote':
|
||||
quote.votes -= 1
|
||||
elif action == 'downvote':
|
||||
quote.votes += 1
|
||||
del vote_data[str(id)] # Remove the vote from the cookie
|
||||
flash("Your vote has been undone.", 'success')
|
||||
else:
|
||||
# User is switching their vote (upvote to downvote or vice versa)
|
||||
if action == 'upvote':
|
||||
quote.votes += 2 # Switch from downvote to upvote
|
||||
elif action == 'downvote':
|
||||
quote.votes -= 2 # Switch from upvote to downvote
|
||||
vote_data[str(id)] = action # Update the action in the cookie
|
||||
flash("Your vote has been changed.", 'success')
|
||||
else:
|
||||
# User has not voted on this quote before
|
||||
if action == 'upvote':
|
||||
quote.votes += 1
|
||||
elif action == 'downvote':
|
||||
quote.votes -= 1
|
||||
vote_data[str(id)] = action # Record the vote in the cookie
|
||||
flash("Thank you for voting!", 'success')
|
||||
|
||||
# Save the updated vote data in the cookie
|
||||
try:
|
||||
db.session.commit()
|
||||
page = request.args.get('page', 1) # Get the current page number from query params
|
||||
resp = make_response(redirect(url_for('browse', page=page)))
|
||||
resp.set_cookie('votes', json.dumps(vote_data), max_age=60*60*24*365) # 1 year expiration
|
||||
return resp
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
flash("Error voting on quote: {}".format(e), 'error')
|
||||
return redirect(url_for('browse', page=page))
|
||||
|
||||
|
||||
# Route for displaying a random quote
|
||||
@app.route('/random')
|
||||
def random_quote():
|
||||
count = Quote.query.count()
|
||||
if count == 0:
|
||||
flash("No quotes available yet.", 'error')
|
||||
return redirect(url_for('index'))
|
||||
|
||||
random_id = random.randint(1, count)
|
||||
random_quote = Quote.query.get(random_id)
|
||||
|
||||
# If the random quote is not found (i.e., deleted), pick another random quote
|
||||
while random_quote is None:
|
||||
random_id = random.randint(1, count)
|
||||
random_quote = Quote.query.get(random_id)
|
||||
|
||||
return render_template('random.html', quote=random_quote)
|
||||
|
||||
@app.route('/quote')
|
||||
def quote():
|
||||
quote_id = request.args.get('id')
|
||||
if not quote_id:
|
||||
flash("Quote ID not provided.", 'error')
|
||||
return redirect(url_for('browse'))
|
||||
|
||||
quote = Quote.query.get_or_404(quote_id)
|
||||
return render_template('quote.html', quote=quote)
|
||||
|
||||
# Admin login route
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
|
||||
# Check if the username is correct and verify the password using Argon2
|
||||
if username == ADMIN_CREDENTIALS['username']:
|
||||
try:
|
||||
ph.verify(ADMIN_CREDENTIALS['password'], password) # Verify password using Argon2
|
||||
session['admin'] = True
|
||||
flash('Login successful!', 'success')
|
||||
return redirect(url_for('modapp'))
|
||||
except VerifyMismatchError:
|
||||
flash('Invalid password. Please try again.', 'danger')
|
||||
else:
|
||||
flash('Invalid username. Please try again.', 'danger')
|
||||
|
||||
return render_template('login.html')
|
||||
|
||||
# Admin panel route (accessible only to logged-in admins)
|
||||
@app.route('/modapp')
|
||||
def modapp():
|
||||
if not session.get('admin'): # If admin not logged in, redirect to login
|
||||
flash('You need to log in first.', 'danger')
|
||||
return redirect(url_for('login'))
|
||||
|
||||
pending_quotes = Quote.query.filter_by(status=0).all()
|
||||
return render_template('modapp.html', pending_quotes=pending_quotes)
|
||||
|
||||
# Route for browsing approved quotes
|
||||
@app.route('/browse', methods=['GET'])
|
||||
def browse():
|
||||
page = request.args.get('page', 1, type=int)
|
||||
quotes = Quote.query.filter_by(status=1).order_by(Quote.date.desc()).paginate(page=page, per_page=10)
|
||||
return render_template('browse.html', quotes=quotes)
|
||||
|
||||
# Approve a quote (admin only)
|
||||
@app.route('/approve/<int:id>')
|
||||
def approve(id):
|
||||
if not session.get('admin'):
|
||||
return redirect(url_for('login'))
|
||||
|
||||
quote = Quote.query.get_or_404(id)
|
||||
quote.status = 1 # 1 = approved
|
||||
db.session.commit()
|
||||
return redirect(url_for('modapp'))
|
||||
|
||||
# Reject a quote (admin only)
|
||||
@app.route('/reject/<int:id>')
|
||||
def reject(id):
|
||||
if not session.get('admin'):
|
||||
return redirect(url_for('login'))
|
||||
|
||||
quote = Quote.query.get_or_404(id)
|
||||
quote.status = 2 # 2 = rejected
|
||||
db.session.commit()
|
||||
return redirect(url_for('modapp'))
|
||||
|
||||
# Delete a quote (admin only)
|
||||
@app.route('/delete/<int:id>')
|
||||
def delete(id):
|
||||
if not session.get('admin'):
|
||||
return redirect(url_for('login'))
|
||||
|
||||
quote = Quote.query.get_or_404(id)
|
||||
db.session.delete(quote)
|
||||
db.session.commit()
|
||||
return redirect(url_for('modapp'))
|
||||
|
||||
# Admin logout route
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
session.pop('admin', None)
|
||||
flash('Logged out successfully.', 'success')
|
||||
return redirect(url_for('login'))
|
||||
|
||||
# Automatically create the database tables using app context
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
||||
# Run the Flask app
|
||||
if __name__ == "__main__":
|
||||
app.run(debug=True)
|
||||
Reference in New Issue
Block a user