Article Management System
A comprehensive Flask-based Content Management System (CMS) designed for managing articles with advanced SEO optimization, admin panel, and automated maintenance features.
Features
Core Functionality
- Article Management: Create, read, update, and delete articles with server-side rendering
- SEO Optimization:
- Meta tag management (title, description, keywords)
- JSON-LD schema.org structured data
- Self-canonicalization
- Descriptive URLs (auto-generated slugs)
- SEO scoring and recommendations
- HTML Import: Automatically parse and extract article data from HTML with schema.org markup
- Admin Panel: Full-featured admin interface for content management
- Responsive Design: Mobile-friendly horizontal card layout with Bootstrap 5
- Pagination: Efficiently display 9 articles per page with full pagination support
- User Authentication: Secure login/registration system with role-based access control
- Comments System: Integrated with rcomments package for threaded comments
- Likes/Dislikes: User engagement tracking
- View Tracking: Automatic view count incrementation
- Maintenance Automation:
- Auto-delete articles older than configurable threshold (default: 30 days)
- Keep maximum number of articles (default: 90)
- Scheduled daily maintenance with APScheduler
- Manual maintenance trigger
API Endpoints
Full RESTful API for React/TypeScript frontend integration:
GET /api/articles- Paginated article listingGET /api/article/<slug>- Single article detailsGET /api/article/<slug>/comments- Comments with paginationPOST /api/article/<slug>/comment- Add comment (authenticated)DELETE /api/comment/<id>- Delete commentPOST /api/article/<slug>/like- Toggle like/dislikeGET /api/admin/analytics- Admin analytics dashboardPOST /api/admin/maintenance/run- Manual maintenance triggerPOST /api/admin/seo/analyze- SEO score analysis
Installation
Prerequisites
- Python 3.8+
- PostgreSQL 10+
- pip (Python package installer)
Basic Installation
# Install from PyPI
pip install article-management-system
Database Setup
- Create a PostgreSQL database:
CREATE DATABASE article_management;
CREATE USER article_user WITH PASSWORD 'your-password';
GRANT ALL PRIVILEGES ON DATABASE article_management TO article_user;
- Configure environment variables (or use
.envfile):
export DATABASE_URL=postgresql://article_user:your-password@localhost:5432/article_management
export SECRET_KEY=your-secret-key-here
export SITE_URL=http://yourdomain.com
Initialize Database
# Initialize database and create default admin user
flask init-db
# Or using the CLI command
ams-cli init-db
Configuration
Environment Variables
| Variable | Default | Description |
|---|---|---|
SECRET_KEY |
(required) | Flask secret key for sessions |
DATABASE_URL |
(required) | PostgreSQL connection URL |
SITE_URL |
http://localhost:5000 |
Base URL of your site |
SITE_NAME |
Article Management System |
Site name for meta tags |
ARTICLES_PER_PAGE |
9 |
Number of articles per page |
MAINTENANCE_DAYS_THRESHOLD |
30 |
Delete articles older than X days |
MAINTENANCE_MAX_ARTICLES |
90 |
Maximum articles to keep |
RCOMMENTS_ENABLED |
true |
Enable/disable comments |
Database Configuration
The system supports flexible database configuration:
Option 1: Full DATABASE_URL
export DATABASE_URL=postgresql://user:password@localhost:5432/dbname
Option 2: Individual components
export DB_USER=myuser
export DB_PASSWORD=mypassword
export DB_HOST=localhost
export DB_PORT=5432
export DB_NAME=article_management
Option 3: SQLite for development (not recommended for production)
export DATABASE_URL=sqlite:///dev.db
Custom Configuration Class
You can create a custom configuration class to override any setting:
from article_management.config import Config
class CustomConfig(Config):
ARTICLES_PER_PAGE = 12
MAINTENANCE_DAYS_THRESHOLD = 60
SITE_NAME = "My Custom Site"
SITE_URL = "https://mywebsite.com"
# Override any other settings
Then pass it to create_app():
from article_management import create_app
app = create_app('my_module.CustomConfig')
Customization Guide
Custom Authentication (OAuth, SSO, etc.)
The system is designed to be extensible. You can replace or extend the authentication system:
1. Create a custom User model (optional):
# models.py
from article_management import db
from flask_login import UserMixin
class CustomUser(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(120), unique=True, nullable=False)
# Add OAuth fields: oauth_provider, oauth_id, avatar_url, etc.
2. Create custom auth routes:
# routes/custom_auth.py
from flask import Blueprint, redirect, url_for
from flask_login import login_user
custom_auth_bp = Blueprint('custom_auth', __name__)
@custom_auth_bp.route('/login/google')
def google_login():
# Implement OAuth flow
# Redirect to Google OAuth endpoint
pass
@custom_auth_bp.route('/login/google/callback')
def google_callback():
# Handle OAuth callback
# Get or create user
# login_user(user)
return redirect(url_for('main.index'))
3. Override the auth blueprint:
# In your application factory
def create_app(config_class='article_management.config.Config'):
app = Flask(__name__)
app.config.from_object(config_class)
# Don't register the default auth blueprint
# from article_management.routes.auth import bp as auth_bp
# app.register_blueprint(auth_bp, url_prefix='/auth')
# Register your custom auth instead
from myapp.routes.custom_auth import custom_auth_bp
app.register_blueprint(custom_auth_bp, url_prefix='/auth')
4. Use Flask-Dance or Authlib (recommended for OAuth):
from flask_dance.contrib.google import make_google_blueprint
google_bp = make_google_blueprint(
client_id=os.getenv('GOOGLE_CLIENT_ID'),
client_secret=os.getenv('GOOGLE_CLIENT_SECRET'),
scope=["profile", "email"]
)
app.register_blueprint(google_bp, url_prefix="/login")
Custom Templates
Override any template by creating a templates/ directory and setting TEMPLATES_PATH in your config:
class CustomConfig(Config):
TEMPLATES_PATH = ['/path/to/custom/templates', 'article_management/templates']
Flask will search paths in order, allowing you to override specific templates while keeping the rest.
Custom Static Files
Add custom CSS/JS by placing files in your own static directory:
class CustomConfig(Config):
STATIC_FOLDER = '/path/to/custom/static'
Or use Flask's send_static_file override for more control.
Adding New Routes
Create a new blueprint in routes/ and register it in your app factory:
# routes/custom.py
from flask import Blueprint, render_template
custom_bp = Blueprint('custom', __name__)
@custom_bp.route('/custom-page')
def custom_page():
return render_template('custom_page.html')
Then in your app:
from myapp.routes.custom import custom_bp
app.register_blueprint(custom_bp)
Extending the Article Model
Add custom fields to the Article model by creating a migration:
# In a migration file
op.add_column('articles', sa.Column('custom_field', sa.String(500)))
Then update your forms and templates accordingly.
Latest Updates (v1.1.0)
Recent Improvements
-
Enhanced Reading Experience (March 2026)
- Fixed article content width alignment with title (now both 800px)
- Improved typography with serif fonts (Georgia, Times New Roman)
- Better line-height (1.8) and font size (1.125rem)
- Text justification with hyphenation for clean flow
- Enhanced heading hierarchy with visual styling
- Improved blockquotes, lists, code blocks, and tables
- Responsive design with mobile-friendly padding
-
Maintenance Fixes (March 2026)
- Fixed
MaintenanceSettings.run_maintenance()to work regardless ofis_enabledflag - Manual "Run Maintenance Now" button now always executes
- Fixed missing
MaintenanceSettingsimport in API routes - Removed CSRF exemption issue from API endpoint
- Fixed
-
Bug Fixes
- Fixed import errors in API routes
- Improved error handling in maintenance tasks
API Reference
Authentication Endpoints
GET /auth/login- Login pagePOST /auth/login- Login submissionGET /auth/register- Registration pagePOST /auth/register- Registration submissionGET /auth/logout- Logout
Article Endpoints
GET /api/articles- Paginated article listing- Query params:
page,per_page,sort_by(date_published, view_count),order(asc, desc)
- Query params:
GET /api/article/<slug>- Single article details with JSON-LDGET /api/article/<slug>/comments- Paginated commentsPOST /api/article/<slug>/comment- Add comment (requires auth)DELETE /api/comment/<id>- Delete comment (requires admin or comment author)POST /api/article/<slug>/like- Toggle like/dislike (requires auth)
Admin Endpoints
GET /api/admin/analytics- Analytics dashboard dataPOST /api/admin/maintenance/run- Manually trigger maintenance (requires admin)POST /api/admin/seo/analyze- SEO score analysis (requires admin)
CLI Commands
The package includes a powerful CLI:
# Initialize database and create admin
ams-cli init-db
# Create admin user
ams-cli create-admin
# List all articles
ams-cli list-articles
# Delete article by ID
ams-cli delete-article 123
# Run maintenance manually
ams-cli run-maintenance
# Export all articles to JSON
ams-cli export-articles -o backup.json
Project Structure
article_management/
├── __init__.py # Application factory
├── config.py # Configuration classes
├── models.py # Database models
├── seo_utils.py # SEO utilities
├── html_parser.py # HTML content parser
├── forms.py # WTForms definitions
├── decorators.py # Custom decorators
├── cli.py # CLI commands
├── routes/
│ ├── __init__.py
│ ├── main.py # Public routes
│ ├── admin.py # Admin routes
│ ├── auth.py # Authentication routes
│ └── api.py # API endpoints
├── templates/ # Jinja2 templates
│ ├── base.html
│ ├── index.html
│ ├── article_detail.html
│ ├── auth/
│ └── admin/
├── static/
│ ├── css/style.css
│ └── js/main.js
└── wsgi.py # WSGI entry point
Database Schema
Main Tables
- users: User accounts with authentication
- articles: Article content with SEO fields
- comments: Threaded comments (integrated with rcomments)
- likes: User likes/dislikes tracking
- maintenance_settings: Maintenance configuration
Relationships
- User → Comments (one-to-many)
- User → Likes (one-to-many)
- Article → Comments (one-to-many, cascade delete)
- Article → Likes (one-to-many, cascade delete)
- Comment → Replies (self-referential)
Deployment
Production Configuration
- Environment Setup:
export FLASK_ENV=production
export SECRET_KEY=your-very-secure-secret-key
export DATABASE_URL=postgresql://user:pass@host:5432/db
- Use a Production WSGI Server:
# Using Gunicorn
pip install gunicorn
gunicorn -w 4 "article_management.wsgi:app" -b 0.0.0.0:8000
- Set up Systemd service (Linux):
[Unit]
Description=Article Management System
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/path/to/app
Environment="PATH=/path/to/venv/bin"
EnvironmentFile=/path/to/.env
ExecStart=/path/to/venv/bin/gunicorn -w 4 "article_management.wsgi:app" -b 127.0.0.1:8000
[Install]
WantedBy=multi-user.target
- Nginx Configuration:
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Database Migrations
# Initialize migrations
flask db init
# Create migration
flask db migrate -m "Description"
# Apply migration
flask db upgrade
Testing
Run tests with pytest:
pytest tests/
Security Features
- Password hashing with werkzeug
- CSRF protection with Flask-WTF
- SQL injection prevention with SQLAlchemy
- XSS protection in templates
- Authentication required for admin routes
- Role-based access control (admin/user)
- Secure session management
Extending the Package
Custom Templates
Override templates by creating a templates/ directory and setting TEMPLATES_PATH in config.
Custom Static Files
Add custom CSS/JS by placing files in static/ directory.
Adding New Routes
Create a new blueprint in routes/ and register it in __init__.py.
Troubleshooting
Common Issues
-
Database connection error:
- Check PostgreSQL is running
- Verify credentials in
DATABASE_URL - Ensure database exists
-
Template not found:
- Check Flask can find the templates directory
- Verify package installation with
pip install -e .
-
Static files not loading:
- Ensure
static/folder exists - Check file permissions
- Ensure
-
Migrations fail:
- Ensure database user has proper privileges
- Check Flask-Migrate is installed
License
MIT License - see LICENSE file for details
Changelog
v1.1.0 (March 2026)
- Reading Experience: Completely redesigned article layout with consistent 800px width, serif typography, and professional publishing-quality styling
- Maintenance Fixes: Manual maintenance now works correctly regardless of auto-maintenance setting
- Bug Fixes: Fixed missing MaintenanceSettings import in API routes
- Code Quality: Removed problematic CSRF import, improved error handling
v1.0.0
- Initial release
- Full CRUD operations for articles
- SEO optimization with schema.org
- HTML import with automatic parsing
- Admin dashboard
- User authentication
- Comments system (rcomments integration)
- Likes/dislikes
- Maintenance automation
- REST API for frontend integration
- Responsive Bootstrap 5 UI
Usage
Running the Application
# Set Flask app
export FLASK_APP=article_management.wsgi:app
export FLASK_ENV=development
# Run development server
flask run
Or using the WSGI file directly:
python -m article_management.wsgi
Admin Panel
Access the admin panel at /admin after logging in with admin credentials.
Features:
- Dashboard with statistics
- Create articles by uploading HTML or pasting content
- Edit existing articles
- Manage all articles with search/filter
- Configure maintenance settings
- Run maintenance manually
- SEO analysis tool
Creating Articles
Method 1: HTML Upload
Upload an HTML file with schema.org Article markup:
<article itemscope itemtype="https://schema.org/Article">
<header>
<h1 itemprop="headline">Article Title</h1>
<meta itemprop="datePublished" content="2026-03-24">
<meta itemprop="dateModified" content="2026-03-24">
</header>
<div itemprop="articleBody">
<h2>Section Title</h2>
<p>Article content...</p>
</div>
</article>
Method 2: Manual Entry
Fill in all fields manually through the admin form.
Required HTML Structure:
<article itemscope itemtype="https://schema.org/Article"><h1 itemprop="headline">- Article title<meta itemprop="datePublished">- Publication date<meta itemprop="dateModified">- Last modified date<div itemprop="articleBody">- Main content
The system will automatically:
- Extract title, dates, and excerpt from HTML
- Generate SEO-friendly slug from title
- Create JSON-LD schema
- Set canonical URL (self-canonicalization by default)
SEO Features
The system provides comprehensive SEO optimization:
- Meta Tags: Title, description, keywords
- JSON-LD Schema: Automatic generation of schema.org Article markup
- Canonical URLs: Self-canonicalization or custom URLs
- Descriptive Slugs: Auto-generated from title, unique
- SEO Scoring: Real-time analysis with recommendations
- Open Graph: Automatic OG tags for social sharing
- Twitter Cards: Enhanced Twitter sharing
Maintenance Automation
The system automatically cleans up old articles based on your settings:
- Daily Schedule: Runs every day at 2 AM
- Deletion Rules:
- Delete articles older than
MAINTENANCE_DAYS_THRESHOLD - If total articles still exceed
MAINTENANCE_MAX_ARTICLES, delete oldest articles
- Delete articles older than
- Manual Trigger: Run maintenance anytime from admin panel or CLI
API Integration with React/TypeScript
The system provides a complete REST API for frontend integration:
// Example React component
import React, { useEffect, useState } from 'react';
import { Article } from './types';
const ArticleList: React.FC = () => {
const [articles, setArticles] = useState<Article[]>([]);
const [page, setPage] = useState(1);
useEffect(() => {
fetch(`/api/articles?page=${page}`)
.then(res => res.json())
.then(data => setArticles(data.data.articles));
}, [page]);
return (
<div>
{articles.map(article => (
<ArticleCard key={article.id} article={article} />
))}
</div>
);
};
CLI Commands
The package includes a powerful CLI:
# Initialize database and create admin
ams-cli init-db
# Create admin user
ams-cli create-admin
# List all articles
ams-cli list-articles
# Delete article by ID
ams-cli delete-article 123
# Run maintenance manually
ams-cli run-maintenance
# Export all articles to JSON
ams-cli export-articles -o backup.json
Project Structure
article_management/
├── __init__.py # Application factory
├── config.py # Configuration classes
├── models.py # Database models
├── seo_utils.py # SEO utilities
├── html_parser.py # HTML content parser
├── forms.py # WTForms definitions
├── decorators.py # Custom decorators
├── cli.py # CLI commands
├── routes/
│ ├── __init__.py
│ ├── main.py # Public routes
│ ├── admin.py # Admin routes
│ ├── auth.py # Authentication routes
│ └── api.py # API endpoints
├── templates/ # Jinja2 templates
│ ├── base.html
│ ├── index.html
│ ├── article_detail.html
│ ├── auth/
│ └── admin/
├── static/
│ ├── css/style.css
│ └── js/main.js
└── wsgi.py # WSGI entry point
Database Schema
Main Tables
- users: User accounts with authentication
- articles: Article content with SEO fields
- comments: Threaded comments (integrated with rcomments)
- likes: User likes/dislikes tracking
- maintenance_settings: Maintenance configuration
Relationships
- User → Comments (one-to-many)
- User → Likes (one-to-many)
- Article → Comments (one-to-many, cascade delete)
- Article → Likes (one-to-many, cascade delete)
- Comment → Replies (self-referential)
Deployment
Production Configuration
- Environment Setup:
export FLASK_ENV=production
export SECRET_KEY=your-very-secure-secret-key
export DATABASE_URL=postgresql://user:pass@host:5432/db
- Use a Production WSGI Server:
# Using Gunicorn
pip install gunicorn
gunicorn -w 4 "article_management.wsgi:app" -b 0.0.0.0:8000
- Set up Systemd service (Linux):
[Unit]
Description=Article Management System
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/path/to/app
Environment="PATH=/path/to/venv/bin"
EnvironmentFile=/path/to/.env
ExecStart=/path/to/venv/bin/gunicorn -w 4 "article_management.wsgi:app" -b 127.0.0.1:8000
[Install]
WantedBy=multi-user.target
- Nginx Configuration:
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Database Migrations
# Initialize migrations
flask db init
# Create migration
flask db migrate -m "Description"
# Apply migration
flask db upgrade
Testing
Run tests with pytest:
pytest tests/
Security Features
- Password hashing with werkzeug
- CSRF protection with Flask-WTF
- SQL injection prevention with SQLAlchemy
- XSS protection in templates
- Authentication required for admin routes
- Role-based access control (admin/user)
- Secure session management
Extending the Package
Custom Templates
Override templates by creating a templates/ directory and setting TEMPLATES_PATH in config.
Custom Static Files
Add custom CSS/JS by placing files in static/ directory.
Adding New Routes
Create a new blueprint in routes/ and register it in __init__.py.
Troubleshooting
Common Issues
-
Database connection error:
- Check PostgreSQL is running
- Verify credentials in
DATABASE_URL - Ensure database exists
-
Template not found:
- Check Flask can find the templates directory
- Verify package installation with
pip install -e .
-
Static files not loading:
- Ensure
static/folder exists - Check file permissions
- Ensure
-
Migrations fail:
- Ensure database user has proper privileges
- Check Flask-Migrate is installed
License
MIT License - see LICENSE file for details
Changelog
v1.0.0
- Initial release
- Full CRUD operations for articles
- SEO optimization with schema.org
- HTML import with automatic parsing
- Admin dashboard
- User authentication
- Comments system (rcomments integration)
- Likes/dislikes
- Maintenance automation
- REST API for frontend integration
- Responsive Bootstrap 5 UI
- CLI tools
Metadata
Release files for article-management-system 1.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| article_management_system-1.1.1.tar.gz | 57.7 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| article_management_system-1.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 123.4 kB
Release files / article_management_system-1.1.1.tar.gz
| Download URL | article_management_system-1.1.1.tar.gz |
|---|---|
| Size | 57.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
88a2cf659dae97ac33cfbd836e01e92be68695f63743a572aa51be08fe532164
|
|
BLAKE2b-256 checksum How to use checksums |
8dbe6d2e0b44fc66d1350e4ec5ca3cbe6726d864ecb817b21e3a35fa93d29ca4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.3
|
Release files / article_management_system-1.1.1-py3-none-any.whl
| Download URL | article_management_system-1.1.1-py3-none-any.whl |
|---|---|
| Size | 65.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1e50d8d233978a30184b9809e3a623507653a3c5ca6c690b16f9aeb1c50dcaf7
|
|
BLAKE2b-256 checksum How to use checksums |
5750deca9e4bcd5d4b578fd7c67fc72ae4d5733919a6e6d48cf1ac7ba0826f97
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.3
|
Security for article-management-system 1.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.