319 lines
13 KiB
Python
319 lines
13 KiB
Python
import os
|
|
from datetime import datetime
|
|
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify
|
|
from slugify import slugify
|
|
|
|
from config import config
|
|
from models import db, User, BlogPost, ForumCategory, ForumTopic, ForumReply, ContactMessage
|
|
|
|
|
|
def create_app(config_name=None):
|
|
if config_name is None:
|
|
config_name = os.environ.get('FLASK_CONFIG', 'default')
|
|
|
|
app = Flask(__name__)
|
|
app.config.from_object(config[config_name])
|
|
|
|
db.init_app(app)
|
|
|
|
with app.app_context():
|
|
db.create_all()
|
|
seed_data()
|
|
|
|
# Context processor for templates
|
|
@app.context_processor
|
|
def inject_now():
|
|
return {'now': datetime.utcnow()}
|
|
|
|
# ==================== Company Pages ====================
|
|
|
|
@app.route('/')
|
|
def index():
|
|
latest_posts = BlogPost.query.filter_by(published=True)\
|
|
.order_by(BlogPost.created_at.desc())\
|
|
.limit(3).all()
|
|
return render_template('index.html', latest_posts=latest_posts)
|
|
|
|
@app.route('/about')
|
|
def about():
|
|
return render_template('about.html')
|
|
|
|
@app.route('/services')
|
|
def services():
|
|
return render_template('services.html')
|
|
|
|
@app.route('/contact')
|
|
def contact():
|
|
return render_template('contact.html')
|
|
|
|
@app.route('/contact/submit', methods=['POST'])
|
|
def contact_submit():
|
|
name = request.form.get('name')
|
|
email = request.form.get('email')
|
|
subject = request.form.get('subject')
|
|
message = request.form.get('message')
|
|
|
|
if not all([name, email, subject, message]):
|
|
return '<div class="p-4 bg-red-100 text-red-700 rounded-lg">Please fill in all fields.</div>'
|
|
|
|
contact_message = ContactMessage(
|
|
name=name,
|
|
email=email,
|
|
subject=subject,
|
|
message=message
|
|
)
|
|
db.session.add(contact_message)
|
|
db.session.commit()
|
|
|
|
return '''<div class="p-4 bg-green-100 text-green-700 rounded-lg">
|
|
<strong>Thank you!</strong> Your message has been sent. We'll get back to you soon.
|
|
</div>'''
|
|
|
|
# ==================== Blog ====================
|
|
|
|
@app.route('/blog')
|
|
def blog_index():
|
|
page = request.args.get('page', 1, type=int)
|
|
posts = BlogPost.query.filter_by(published=True)\
|
|
.order_by(BlogPost.created_at.desc())\
|
|
.paginate(page=page, per_page=10, error_out=False)
|
|
return render_template('blog/index.html', posts=posts.items)
|
|
|
|
@app.route('/blog/search')
|
|
def blog_search():
|
|
query = request.args.get('search', '')
|
|
if query:
|
|
posts = BlogPost.query.filter_by(published=True)\
|
|
.filter(BlogPost.title.ilike(f'%{query}%') | BlogPost.content.ilike(f'%{query}%'))\
|
|
.order_by(BlogPost.created_at.desc())\
|
|
.limit(10).all()
|
|
else:
|
|
posts = BlogPost.query.filter_by(published=True)\
|
|
.order_by(BlogPost.created_at.desc())\
|
|
.limit(10).all()
|
|
return render_template('blog/partials/post_list.html', posts=posts)
|
|
|
|
@app.route('/blog/load-more/<int:page>')
|
|
def blog_load_more(page):
|
|
posts = BlogPost.query.filter_by(published=True)\
|
|
.order_by(BlogPost.created_at.desc())\
|
|
.paginate(page=page, per_page=10, error_out=False)
|
|
return render_template('blog/partials/post_list.html', posts=posts.items)
|
|
|
|
@app.route('/blog/<slug>')
|
|
def blog_post(slug):
|
|
post = BlogPost.query.filter_by(slug=slug, published=True).first_or_404()
|
|
return render_template('blog/post.html', post=post)
|
|
|
|
# ==================== Forum ====================
|
|
|
|
@app.route('/forum')
|
|
def forum_index():
|
|
categories = ForumCategory.query.order_by(ForumCategory.order).all()
|
|
recent_topics = ForumTopic.query\
|
|
.order_by(ForumTopic.created_at.desc())\
|
|
.limit(5).all()
|
|
return render_template('forum/index.html', categories=categories, recent_topics=recent_topics)
|
|
|
|
@app.route('/forum/search')
|
|
def forum_search():
|
|
query = request.args.get('q', '')
|
|
if query:
|
|
topics = ForumTopic.query\
|
|
.filter(ForumTopic.title.ilike(f'%{query}%') | ForumTopic.content.ilike(f'%{query}%'))\
|
|
.order_by(ForumTopic.created_at.desc())\
|
|
.limit(10).all()
|
|
|
|
if topics:
|
|
html = '<div class="card mb-4"><h3 class="text-lg font-semibold mb-4">Search Results</h3><div class="space-y-2">'
|
|
for topic in topics:
|
|
html += f'''<a href="{url_for('forum_topic', category_slug=topic.category.slug, topic_id=topic.id)}"
|
|
class="block p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors">
|
|
<div class="font-medium text-gray-900">{topic.title}</div>
|
|
<div class="text-sm text-gray-500">in {topic.category.name}</div>
|
|
</a>'''
|
|
html += '</div></div>'
|
|
return html
|
|
else:
|
|
return '<div class="card mb-4 text-center text-gray-500 py-4">No topics found matching your search.</div>'
|
|
return ''
|
|
|
|
@app.route('/forum/<slug>')
|
|
def forum_category(slug):
|
|
category = ForumCategory.query.filter_by(slug=slug).first_or_404()
|
|
page = request.args.get('page', 1, type=int)
|
|
topics = ForumTopic.query.filter_by(category_id=category.id)\
|
|
.order_by(ForumTopic.is_pinned.desc(), ForumTopic.created_at.desc())\
|
|
.paginate(page=page, per_page=20, error_out=False)
|
|
return render_template('forum/category.html', category=category, topics=topics)
|
|
|
|
@app.route('/forum/<category_slug>/new', methods=['POST'])
|
|
def forum_create_topic(category_slug):
|
|
category = ForumCategory.query.filter_by(slug=category_slug).first_or_404()
|
|
|
|
title = request.form.get('title')
|
|
content = request.form.get('content')
|
|
|
|
if not title or not content:
|
|
return '<div class="p-4 bg-red-100 text-red-700 rounded-lg mb-4">Please fill in all fields.</div>'
|
|
|
|
# Get or create a default user for demo purposes
|
|
user = User.query.first()
|
|
if not user:
|
|
user = User(username='Guest', email='guest@example.com')
|
|
user.set_password('guest')
|
|
db.session.add(user)
|
|
db.session.commit()
|
|
|
|
topic = ForumTopic(
|
|
title=title,
|
|
content=content,
|
|
category_id=category.id,
|
|
author_id=user.id
|
|
)
|
|
db.session.add(topic)
|
|
db.session.commit()
|
|
|
|
# Return updated topic list
|
|
topics = ForumTopic.query.filter_by(category_id=category.id)\
|
|
.order_by(ForumTopic.is_pinned.desc(), ForumTopic.created_at.desc())\
|
|
.paginate(page=1, per_page=20, error_out=False)
|
|
return render_template('forum/partials/topic_list.html', category=category, topics=topics)
|
|
|
|
@app.route('/forum/<category_slug>/<int:topic_id>')
|
|
def forum_topic(category_slug, topic_id):
|
|
topic = ForumTopic.query.get_or_404(topic_id)
|
|
replies = topic.replies.order_by(ForumReply.created_at.asc()).all()
|
|
return render_template('forum/topic.html', topic=topic, replies=replies)
|
|
|
|
@app.route('/forum/<category_slug>/<int:topic_id>/reply', methods=['POST'])
|
|
def forum_add_reply(category_slug, topic_id):
|
|
topic = ForumTopic.query.get_or_404(topic_id)
|
|
|
|
if topic.is_locked:
|
|
return '<div class="p-4 bg-red-100 text-red-700 rounded-lg">This topic is locked.</div>'
|
|
|
|
content = request.form.get('content')
|
|
if not content:
|
|
return '<div class="p-4 bg-red-100 text-red-700 rounded-lg">Please enter a reply.</div>'
|
|
|
|
# Get or create a default user for demo purposes
|
|
user = User.query.first()
|
|
if not user:
|
|
user = User(username='Guest', email='guest@example.com')
|
|
user.set_password('guest')
|
|
db.session.add(user)
|
|
db.session.commit()
|
|
|
|
reply = ForumReply(
|
|
content=content,
|
|
topic_id=topic.id,
|
|
author_id=user.id
|
|
)
|
|
db.session.add(reply)
|
|
db.session.commit()
|
|
|
|
# Return the new reply as HTML
|
|
return f'''<div class="card mb-4">
|
|
<div class="flex items-start">
|
|
<div class="w-10 h-10 bg-gray-100 rounded-full flex items-center justify-center text-gray-600 font-semibold flex-shrink-0">
|
|
{reply.author.username[0].upper()}
|
|
</div>
|
|
<div class="ml-4 flex-grow">
|
|
<div class="flex items-center justify-between mb-2">
|
|
<div class="font-medium text-gray-900">{reply.author.username}</div>
|
|
<div class="text-sm text-gray-500">{reply.created_at.strftime('%B %d, %Y at %H:%M')}</div>
|
|
</div>
|
|
<div class="prose prose-sm max-w-none text-gray-600">
|
|
{reply.content}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>'''
|
|
|
|
return app
|
|
|
|
|
|
def seed_data():
|
|
"""Seed the database with sample data if empty."""
|
|
if User.query.first() is None:
|
|
# Create admin user
|
|
admin = User(username='admin', email='admin@directlx.dev', is_admin=True)
|
|
admin.set_password('admin123')
|
|
db.session.add(admin)
|
|
|
|
# Create sample blog posts
|
|
posts = [
|
|
{
|
|
'title': 'Getting Started with Flask and HTMX',
|
|
'slug': 'getting-started-flask-htmx',
|
|
'excerpt': 'Learn how to build modern, interactive web applications using Flask and HTMX.',
|
|
'content': '''<p>Flask and HTMX make a powerful combination for building interactive web applications without the complexity of a JavaScript framework.</p>
|
|
<h2>Why HTMX?</h2>
|
|
<p>HTMX allows you to access modern browser features directly from HTML, making it easy to build dynamic user interfaces with minimal JavaScript.</p>
|
|
<h2>Getting Started</h2>
|
|
<p>First, create a new Flask project and install the required dependencies. Then, add HTMX to your templates and start building interactive features.</p>''',
|
|
'published': True
|
|
},
|
|
{
|
|
'title': 'Building Scalable APIs with Python',
|
|
'slug': 'building-scalable-apis-python',
|
|
'excerpt': 'Best practices for designing and building scalable REST APIs using Python.',
|
|
'content': '''<p>Building scalable APIs requires careful planning and adherence to best practices.</p>
|
|
<h2>Design Principles</h2>
|
|
<p>Follow REST principles, use proper HTTP methods, and design your endpoints to be intuitive and consistent.</p>
|
|
<h2>Performance Considerations</h2>
|
|
<p>Implement caching, pagination, and rate limiting to ensure your API can handle high traffic.</p>''',
|
|
'published': True
|
|
},
|
|
{
|
|
'title': 'The Future of Web Development',
|
|
'slug': 'future-web-development',
|
|
'excerpt': 'Exploring emerging trends and technologies shaping the future of web development.',
|
|
'content': '''<p>The web development landscape is constantly evolving. Let's explore what's next.</p>
|
|
<h2>Emerging Trends</h2>
|
|
<p>From WebAssembly to edge computing, new technologies are changing how we build web applications.</p>
|
|
<h2>What This Means for Developers</h2>
|
|
<p>Staying current with these trends will help you build better applications and advance your career.</p>''',
|
|
'published': True
|
|
}
|
|
]
|
|
|
|
for post_data in posts:
|
|
post = BlogPost(author_id=1, **post_data)
|
|
db.session.add(post)
|
|
|
|
# Create forum categories
|
|
categories = [
|
|
{'name': 'General Discussion', 'slug': 'general', 'description': 'General topics and discussions about software development.', 'order': 1},
|
|
{'name': 'Help & Support', 'slug': 'help', 'description': 'Get help with technical problems and questions.', 'order': 2},
|
|
{'name': 'Showcase', 'slug': 'showcase', 'description': 'Share your projects and get feedback from the community.', 'order': 3},
|
|
{'name': 'Off Topic', 'slug': 'off-topic', 'description': 'Everything else that doesn\'t fit elsewhere.', 'order': 4}
|
|
]
|
|
|
|
for cat_data in categories:
|
|
category = ForumCategory(**cat_data)
|
|
db.session.add(category)
|
|
|
|
db.session.commit()
|
|
|
|
# Create sample forum topics
|
|
general = ForumCategory.query.filter_by(slug='general').first()
|
|
if general:
|
|
topic = ForumTopic(
|
|
title='Welcome to the DirectLX Community!',
|
|
content='Welcome everyone! This is a place to discuss software development, share knowledge, and connect with other developers. Feel free to introduce yourself!',
|
|
category_id=general.id,
|
|
author_id=1,
|
|
is_pinned=True
|
|
)
|
|
db.session.add(topic)
|
|
db.session.commit()
|
|
|
|
|
|
# Create the application instance
|
|
app = create_app()
|
|
|
|
if __name__ == '__main__':
|
|
app.run(debug=True, port=5000)
|