mirror of
https://github.com/thecyberlearn/hostinger-django-demo.git
synced 2026-08-18 07:52:56 +00:00
Initial commit: Django demo project for VPS deployment
🎉 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
commit
2f3f1ea867
20
.env.example
Normal file
20
.env.example
Normal file
@ -0,0 +1,20 @@
|
||||
# Django settings
|
||||
SECRET_KEY=your-super-secret-key-here
|
||||
DEBUG=True
|
||||
ALLOWED_HOSTS=localhost,127.0.0.1,yourdomain.com
|
||||
|
||||
# Database settings (choose one approach)
|
||||
|
||||
# Option 1: Use DATABASE_URL (recommended for production)
|
||||
# DATABASE_URL=postgresql://username:password@hostname:port/database_name
|
||||
|
||||
# Option 2: Use individual PostgreSQL settings
|
||||
# USE_POSTGRES=True
|
||||
# DB_NAME=demo_db
|
||||
# DB_USER=demo_user
|
||||
# DB_PASSWORD=your_password
|
||||
# DB_HOST=localhost
|
||||
# DB_PORT=5432
|
||||
|
||||
# Production security settings
|
||||
SECURE_SSL_REDIRECT=False
|
||||
42
.gitignore
vendored
Normal file
42
.gitignore
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Django
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
media/
|
||||
staticfiles/
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Deployment
|
||||
*.pid
|
||||
*.sock
|
||||
146
DEPLOYMENT_GUIDE.md
Normal file
146
DEPLOYMENT_GUIDE.md
Normal file
@ -0,0 +1,146 @@
|
||||
# VPS Deployment Guide
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before starting, make sure you have:
|
||||
- [ ] Ubuntu 20.04+ VPS with root/sudo access
|
||||
- [ ] Your VPS IP address
|
||||
- [ ] SSH key or password for VPS access
|
||||
- [ ] Domain name (optional, can use IP address)
|
||||
|
||||
## Step 1: Prepare Local Files
|
||||
|
||||
Clean up your local project:
|
||||
```bash
|
||||
rm -rf venv/
|
||||
rm -f db.sqlite3
|
||||
rm -rf staticfiles/
|
||||
rm -f requirements_full.txt
|
||||
```
|
||||
|
||||
## Step 2: Upload to VPS
|
||||
|
||||
### Option A: Using SCP (if you have the files locally)
|
||||
```bash
|
||||
# Replace YOUR_VPS_IP with your actual IP
|
||||
scp -r . root@YOUR_VPS_IP:/home/ubuntu/django-demo
|
||||
```
|
||||
|
||||
### Option B: Using Git (recommended)
|
||||
```bash
|
||||
# On your VPS, clone the repository
|
||||
ssh root@YOUR_VPS_IP
|
||||
cd /home/ubuntu
|
||||
git clone YOUR_REPO_URL django-demo
|
||||
```
|
||||
|
||||
## Step 3: Run Deployment Script
|
||||
|
||||
SSH into your VPS and run:
|
||||
```bash
|
||||
ssh root@YOUR_VPS_IP
|
||||
cd /home/ubuntu/django-demo
|
||||
sudo bash deploy/deploy.sh
|
||||
```
|
||||
|
||||
## Step 4: Configure Environment
|
||||
|
||||
Edit the `.env` file:
|
||||
```bash
|
||||
sudo nano /home/ubuntu/django-demo/.env
|
||||
```
|
||||
|
||||
Set these values:
|
||||
```env
|
||||
SECRET_KEY=your-super-secret-key-here-make-it-long-and-random
|
||||
DEBUG=False
|
||||
ALLOWED_HOSTS=your-domain.com,www.your-domain.com,YOUR_VPS_IP
|
||||
DATABASE_URL=postgresql://demo_user:your_password@localhost:5432/demo_db
|
||||
SECURE_SSL_REDIRECT=False # Set to True after SSL setup
|
||||
```
|
||||
|
||||
## Step 5: Update Domain in Nginx
|
||||
|
||||
Edit nginx configuration:
|
||||
```bash
|
||||
sudo nano /etc/nginx/sites-available/django-demo
|
||||
```
|
||||
|
||||
Replace `yourdomain.com` with your actual domain or IP.
|
||||
|
||||
## Step 6: Restart Services
|
||||
|
||||
```bash
|
||||
sudo systemctl restart django-demo
|
||||
sudo systemctl restart nginx
|
||||
```
|
||||
|
||||
## Step 7: Test Deployment
|
||||
|
||||
Check if everything is working:
|
||||
```bash
|
||||
# Check service status
|
||||
sudo systemctl status django-demo
|
||||
sudo systemctl status nginx
|
||||
|
||||
# View logs if there are issues
|
||||
sudo journalctl -u django-demo -f
|
||||
```
|
||||
|
||||
Visit your website: `http://YOUR_VPS_IP` or `http://your-domain.com`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues:
|
||||
|
||||
1. **Service won't start**:
|
||||
```bash
|
||||
sudo journalctl -u django-demo -f
|
||||
```
|
||||
|
||||
2. **Static files not loading**:
|
||||
```bash
|
||||
cd /home/ubuntu/django-demo
|
||||
source venv/bin/activate
|
||||
python manage.py collectstatic --noinput
|
||||
sudo systemctl restart django-demo
|
||||
```
|
||||
|
||||
3. **Database errors**:
|
||||
- Check PostgreSQL is running: `sudo systemctl status postgresql`
|
||||
- Verify database settings in `.env`
|
||||
|
||||
4. **Permission errors**:
|
||||
```bash
|
||||
sudo chown -R www-data:www-data /home/ubuntu/django-demo
|
||||
```
|
||||
|
||||
## Optional: SSL Certificate (Let's Encrypt)
|
||||
|
||||
```bash
|
||||
sudo apt install certbot python3-certbot-nginx
|
||||
sudo certbot --nginx -d your-domain.com
|
||||
```
|
||||
|
||||
After SSL setup, update `.env`:
|
||||
```env
|
||||
SECURE_SSL_REDIRECT=True
|
||||
```
|
||||
|
||||
## Management Commands
|
||||
|
||||
```bash
|
||||
# Restart Django
|
||||
sudo systemctl restart django-demo
|
||||
|
||||
# View logs
|
||||
sudo journalctl -u django-demo -f
|
||||
|
||||
# Update application
|
||||
sudo bash /home/ubuntu/django-demo/deploy/update.sh
|
||||
|
||||
# Access Django shell
|
||||
cd /home/ubuntu/django-demo
|
||||
source venv/bin/activate
|
||||
python manage.py shell
|
||||
```
|
||||
262
README.md
Normal file
262
README.md
Normal file
@ -0,0 +1,262 @@
|
||||
# Django Demo Project
|
||||
|
||||
A production-ready Django application template optimized for VPS hosting deployment. This project serves as a foundation for testing VPS deployment workflows and can be used as a template for future Django projects.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- **Minimal Dependencies**: Clean, lightweight setup with only essential packages
|
||||
- **Environment Configuration**: Production settings managed via environment variables
|
||||
- **Database Flexible**: Works with SQLite for development and PostgreSQL for production
|
||||
- **Static Files Handling**: Configured with WhiteNoise for efficient static file serving
|
||||
- **Contact Form**: Functional contact form with email validation
|
||||
- **Simple Blog System**: Basic blog functionality with admin interface
|
||||
- **Bootstrap UI**: Responsive design using Bootstrap 5
|
||||
- **Production Security**: Security headers, HTTPS support, and production optimizations
|
||||
- **VPS Deployment**: Complete deployment scripts and configuration files
|
||||
|
||||
## 🛠 Tech Stack
|
||||
|
||||
- **Backend**: Django 4.2.7
|
||||
- **Database**: SQLite (development) / PostgreSQL (production)
|
||||
- **Web Server**: Gunicorn + Nginx
|
||||
- **Frontend**: Bootstrap 5, Vanilla JavaScript
|
||||
- **Static Files**: WhiteNoise
|
||||
- **Configuration**: python-decouple
|
||||
|
||||
## 🚀 Quick Start (Development)
|
||||
|
||||
1. **Clone and setup**:
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd hostinger-django-demo
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate # On Windows: venv\Scripts\activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
2. **Configure environment**:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Edit .env with your settings
|
||||
```
|
||||
|
||||
3. **Initialize database**:
|
||||
```bash
|
||||
python manage.py migrate
|
||||
python manage.py createsuperuser
|
||||
python manage.py collectstatic
|
||||
```
|
||||
|
||||
4. **Run development server**:
|
||||
```bash
|
||||
python manage.py runserver
|
||||
```
|
||||
|
||||
5. **Access the application**:
|
||||
- Homepage: http://127.0.0.1:8000
|
||||
- Admin: http://127.0.0.1:8000/admin
|
||||
|
||||
## 🌐 VPS Deployment
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Ubuntu 20.04+ VPS
|
||||
- Root or sudo access
|
||||
- Domain name (optional, can use IP address)
|
||||
|
||||
### Automated Deployment
|
||||
|
||||
1. **Upload project files** to your VPS:
|
||||
```bash
|
||||
scp -r . user@your-vps-ip:/home/ubuntu/django-demo
|
||||
```
|
||||
|
||||
2. **Run deployment script**:
|
||||
```bash
|
||||
cd /home/ubuntu/django-demo
|
||||
sudo bash deploy/deploy.sh
|
||||
```
|
||||
|
||||
3. **Configure your settings**:
|
||||
- Edit `.env` file with production settings
|
||||
- Update domain in `deploy/nginx.conf`
|
||||
- Restart services: `sudo systemctl restart django-demo nginx`
|
||||
|
||||
### Manual Deployment Steps
|
||||
|
||||
If you prefer manual setup, follow these steps:
|
||||
|
||||
1. **Install system packages**:
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install python3 python3-venv python3-pip nginx postgresql postgresql-contrib
|
||||
```
|
||||
|
||||
2. **Setup project**:
|
||||
```bash
|
||||
cd /home/ubuntu/django-demo
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
3. **Configure environment**:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Edit .env with production settings
|
||||
```
|
||||
|
||||
4. **Setup database and static files**:
|
||||
```bash
|
||||
python manage.py migrate
|
||||
python manage.py collectstatic --noinput
|
||||
python manage.py createsuperuser
|
||||
```
|
||||
|
||||
5. **Configure services**:
|
||||
```bash
|
||||
sudo cp deploy/systemd.service /etc/systemd/system/django-demo.service
|
||||
sudo cp deploy/nginx.conf /etc/nginx/sites-available/django-demo
|
||||
sudo ln -s /etc/nginx/sites-available/django-demo /etc/nginx/sites-enabled/
|
||||
sudo systemctl enable django-demo
|
||||
sudo systemctl start django-demo
|
||||
sudo systemctl restart nginx
|
||||
```
|
||||
|
||||
## ⚙️ Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Create a `.env` file based on `.env.example`:
|
||||
|
||||
```env
|
||||
# Required
|
||||
SECRET_KEY=your-super-secret-key-here
|
||||
DEBUG=False
|
||||
ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com
|
||||
|
||||
# Database (choose one method)
|
||||
# Method 1: DATABASE_URL
|
||||
DATABASE_URL=postgresql://username:password@hostname:port/database_name
|
||||
|
||||
# Method 2: Individual settings
|
||||
USE_POSTGRES=True
|
||||
DB_NAME=demo_db
|
||||
DB_USER=demo_user
|
||||
DB_PASSWORD=your_password
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
|
||||
# Security
|
||||
SECURE_SSL_REDIRECT=True # Set to True if using HTTPS
|
||||
```
|
||||
|
||||
### Database Options
|
||||
|
||||
1. **SQLite (Development)**: Default, no additional setup required
|
||||
2. **PostgreSQL (Production)**: Set `USE_POSTGRES=True` or provide `DATABASE_URL`
|
||||
|
||||
## 🔧 Management Commands
|
||||
|
||||
### Development
|
||||
```bash
|
||||
python manage.py runserver # Start development server
|
||||
python manage.py migrate # Apply database migrations
|
||||
python manage.py createsuperuser # Create admin user
|
||||
python manage.py collectstatic # Collect static files
|
||||
```
|
||||
|
||||
### Production
|
||||
```bash
|
||||
sudo systemctl restart django-demo # Restart Django service
|
||||
sudo systemctl status django-demo # Check service status
|
||||
sudo journalctl -u django-demo -f # View live logs
|
||||
sudo bash deploy/update.sh # Deploy updates
|
||||
```
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```
|
||||
django-demo/
|
||||
├── core/ # Main Django app
|
||||
│ ├── templates/ # HTML templates
|
||||
│ ├── static/ # CSS, JS files
|
||||
│ ├── models.py # Database models
|
||||
│ ├── views.py # View functions
|
||||
│ ├── forms.py # Django forms
|
||||
│ └── admin.py # Admin configuration
|
||||
├── demo_project/ # Django project settings
|
||||
│ ├── settings.py # Main settings
|
||||
│ ├── urls.py # URL configuration
|
||||
│ └── wsgi.py # WSGI application
|
||||
├── deploy/ # Deployment files
|
||||
│ ├── deploy.sh # Deployment script
|
||||
│ ├── update.sh # Update script
|
||||
│ ├── gunicorn.conf.py # Gunicorn configuration
|
||||
│ ├── nginx.conf # Nginx configuration
|
||||
│ └── systemd.service # Systemd service file
|
||||
├── requirements.txt # Python dependencies
|
||||
├── .env.example # Environment variables template
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## 🔍 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Static files not loading**:
|
||||
```bash
|
||||
python manage.py collectstatic --noinput
|
||||
sudo systemctl restart django-demo
|
||||
```
|
||||
|
||||
2. **Database connection errors**:
|
||||
- Check `.env` file database settings
|
||||
- Ensure PostgreSQL is running: `sudo systemctl status postgresql`
|
||||
|
||||
3. **Permission errors**:
|
||||
```bash
|
||||
sudo chown -R www-data:www-data /home/ubuntu/django-demo
|
||||
```
|
||||
|
||||
4. **Service not starting**:
|
||||
```bash
|
||||
sudo journalctl -u django-demo -f # Check logs
|
||||
sudo systemctl status django-demo # Check status
|
||||
```
|
||||
|
||||
### Log Files
|
||||
|
||||
- Django logs: `sudo journalctl -u django-demo -f`
|
||||
- Nginx access: `/var/log/nginx/access.log`
|
||||
- Nginx errors: `/var/log/nginx/error.log`
|
||||
- Custom Django logs: `/var/log/django/`
|
||||
|
||||
## 🔒 Security Considerations
|
||||
|
||||
- Change `SECRET_KEY` in production
|
||||
- Set `DEBUG=False` in production
|
||||
- Use HTTPS in production (`SECURE_SSL_REDIRECT=True`)
|
||||
- Regularly update dependencies
|
||||
- Configure firewall (UFW) properly
|
||||
- Use strong passwords for database users
|
||||
|
||||
## 🚀 Deployment Checklist
|
||||
|
||||
- [ ] Update `.env` with production settings
|
||||
- [ ] Set `DEBUG=False`
|
||||
- [ ] Configure proper `ALLOWED_HOSTS`
|
||||
- [ ] Set strong `SECRET_KEY`
|
||||
- [ ] Configure database settings
|
||||
- [ ] Update domain in nginx configuration
|
||||
- [ ] Set up SSL certificate (recommended)
|
||||
- [ ] Configure firewall rules
|
||||
- [ ] Test all functionality
|
||||
|
||||
## 📝 License
|
||||
|
||||
This project is open source and available under the MIT License.
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
This is a demo project, but improvements are welcome! Please feel free to submit issues and pull requests.
|
||||
0
core/__init__.py
Normal file
0
core/__init__.py
Normal file
21
core/admin.py
Normal file
21
core/admin.py
Normal file
@ -0,0 +1,21 @@
|
||||
from django.contrib import admin
|
||||
from .models import Contact, BlogPost
|
||||
|
||||
|
||||
@admin.register(Contact)
|
||||
class ContactAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'email', 'subject', 'created_at']
|
||||
list_filter = ['created_at']
|
||||
search_fields = ['name', 'email', 'subject']
|
||||
readonly_fields = ['created_at']
|
||||
ordering = ['-created_at']
|
||||
|
||||
|
||||
@admin.register(BlogPost)
|
||||
class BlogPostAdmin(admin.ModelAdmin):
|
||||
list_display = ['title', 'is_published', 'created_at', 'updated_at']
|
||||
list_filter = ['is_published', 'created_at']
|
||||
search_fields = ['title', 'content']
|
||||
prepopulated_fields = {'slug': ('title',)}
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
ordering = ['-created_at']
|
||||
6
core/apps.py
Normal file
6
core/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CoreConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'core'
|
||||
27
core/forms.py
Normal file
27
core/forms.py
Normal file
@ -0,0 +1,27 @@
|
||||
from django import forms
|
||||
from .models import Contact
|
||||
|
||||
|
||||
class ContactForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Contact
|
||||
fields = ['name', 'email', 'subject', 'message']
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': 'Your Name'
|
||||
}),
|
||||
'email': forms.EmailInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': 'your.email@example.com'
|
||||
}),
|
||||
'subject': forms.TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': 'Subject'
|
||||
}),
|
||||
'message': forms.Textarea(attrs={
|
||||
'class': 'form-control',
|
||||
'rows': 5,
|
||||
'placeholder': 'Your message...'
|
||||
}),
|
||||
}
|
||||
44
core/migrations/0001_initial.py
Normal file
44
core/migrations/0001_initial.py
Normal file
@ -0,0 +1,44 @@
|
||||
# Generated by Django 4.2.7 on 2025-08-29 16:12
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='BlogPost',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=200)),
|
||||
('slug', models.SlugField(unique=True)),
|
||||
('content', models.TextField()),
|
||||
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('is_published', models.BooleanField(default=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Contact',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('email', models.EmailField(max_length=254)),
|
||||
('subject', models.CharField(max_length=200)),
|
||||
('message', models.TextField()),
|
||||
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
]
|
||||
0
core/migrations/__init__.py
Normal file
0
core/migrations/__init__.py
Normal file
31
core/models.py
Normal file
31
core/models.py
Normal file
@ -0,0 +1,31 @@
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
class Contact(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
email = models.EmailField()
|
||||
subject = models.CharField(max_length=200)
|
||||
message = models.TextField()
|
||||
created_at = models.DateTimeField(default=timezone.now)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} - {self.subject}"
|
||||
|
||||
class Meta:
|
||||
ordering = ['-created_at']
|
||||
|
||||
|
||||
class BlogPost(models.Model):
|
||||
title = models.CharField(max_length=200)
|
||||
slug = models.SlugField(unique=True)
|
||||
content = models.TextField()
|
||||
created_at = models.DateTimeField(default=timezone.now)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
is_published = models.BooleanField(default=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
class Meta:
|
||||
ordering = ['-created_at']
|
||||
30
core/static/core/css/style.css
Normal file
30
core/static/core/css/style.css
Normal file
@ -0,0 +1,30 @@
|
||||
.hero-section {
|
||||
background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
|
||||
}
|
||||
|
||||
.card {
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
}
|
||||
23
core/static/core/js/main.js
Normal file
23
core/static/core/js/main.js
Normal file
@ -0,0 +1,23 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Auto-hide alerts after 5 seconds
|
||||
const alerts = document.querySelectorAll('.alert');
|
||||
alerts.forEach(function(alert) {
|
||||
setTimeout(function() {
|
||||
const bsAlert = new bootstrap.Alert(alert);
|
||||
bsAlert.close();
|
||||
}, 5000);
|
||||
});
|
||||
|
||||
// Smooth scrolling for internal links
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
target.scrollIntoView({
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
63
core/templates/core/about.html
Normal file
63
core/templates/core/about.html
Normal file
@ -0,0 +1,63 @@
|
||||
{% extends 'core/base.html' %}
|
||||
|
||||
{% block title %}About - Django Demo Project{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-5">
|
||||
<div class="row">
|
||||
<div class="col-md-8 mx-auto">
|
||||
<h1 class="mb-4">About This Project</h1>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Purpose</h5>
|
||||
<p class="card-text">This Django demo project is designed as a production-ready template for testing VPS deployment workflows. It serves as a foundation for future Django projects with all the essential configurations already in place.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Features</h5>
|
||||
<ul class="list-unstyled">
|
||||
<li>✓ Minimal dependencies for fast deployment</li>
|
||||
<li>✓ Environment-based configuration</li>
|
||||
<li>✓ SQLite development / PostgreSQL production</li>
|
||||
<li>✓ Static files handling with WhiteNoise</li>
|
||||
<li>✓ Contact form functionality</li>
|
||||
<li>✓ Simple blog system</li>
|
||||
<li>✓ Bootstrap-based responsive UI</li>
|
||||
<li>✓ Production security settings</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Tech Stack</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<ul class="list-unstyled">
|
||||
<li><strong>Backend:</strong> Django 4.2.7</li>
|
||||
<li><strong>Database:</strong> SQLite/PostgreSQL</li>
|
||||
<li><strong>Server:</strong> Gunicorn</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<ul class="list-unstyled">
|
||||
<li><strong>Frontend:</strong> Bootstrap 5</li>
|
||||
<li><strong>Static Files:</strong> WhiteNoise</li>
|
||||
<li><strong>Config:</strong> python-decouple</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<a href="{% url 'contact' %}" class="btn btn-primary">Get in Touch</a>
|
||||
<a href="{% url 'home' %}" class="btn btn-outline-primary">Back to Home</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
63
core/templates/core/base.html
Normal file
63
core/templates/core/base.html
Normal file
@ -0,0 +1,63 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}Django Demo Project{% endblock %}</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
{% load static %}
|
||||
<link rel="stylesheet" href="{% static 'core/css/style.css' %}">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="{% url 'home' %}">Django Demo</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'home' %}">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'blog' %}">Blog</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'about' %}">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'contact' %}">Contact</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
{% if messages %}
|
||||
<div class="container mt-3">
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer class="bg-light text-center text-muted py-4 mt-5">
|
||||
<div class="container">
|
||||
<p>© 2024 Django Demo Project. Built for VPS deployment testing.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
{% load static %}
|
||||
<script src="{% static 'core/js/main.js' %}"></script>
|
||||
</body>
|
||||
</html>
|
||||
31
core/templates/core/blog.html
Normal file
31
core/templates/core/blog.html
Normal file
@ -0,0 +1,31 @@
|
||||
{% extends 'core/base.html' %}
|
||||
|
||||
{% block title %}Blog - Django Demo Project{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-5">
|
||||
<h1 class="mb-4">Blog</h1>
|
||||
|
||||
{% if posts %}
|
||||
<div class="row">
|
||||
{% for post in posts %}
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ post.title }}</h5>
|
||||
<p class="card-text">{{ post.content|truncatewords:30 }}</p>
|
||||
<small class="text-muted">Published on {{ post.created_at|date:"F d, Y" }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-5">
|
||||
<h3 class="text-muted">No blog posts yet</h3>
|
||||
<p>Blog posts will appear here once they're created in the admin panel.</p>
|
||||
<a href="{% url 'home' %}" class="btn btn-primary">Back to Home</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
68
core/templates/core/contact.html
Normal file
68
core/templates/core/contact.html
Normal file
@ -0,0 +1,68 @@
|
||||
{% extends 'core/base.html' %}
|
||||
|
||||
{% block title %}Contact - Django Demo Project{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<h1 class="mb-4">Contact Us</h1>
|
||||
<p class="lead">Have questions about this Django demo project? Get in touch!</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.name.id_for_label }}" class="form-label">Name</label>
|
||||
{{ form.name }}
|
||||
{% if form.name.errors %}
|
||||
<div class="text-danger">{{ form.name.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.email.id_for_label }}" class="form-label">Email</label>
|
||||
{{ form.email }}
|
||||
{% if form.email.errors %}
|
||||
<div class="text-danger">{{ form.email.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.subject.id_for_label }}" class="form-label">Subject</label>
|
||||
{{ form.subject }}
|
||||
{% if form.subject.errors %}
|
||||
<div class="text-danger">{{ form.subject.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.message.id_for_label }}" class="form-label">Message</label>
|
||||
{{ form.message }}
|
||||
{% if form.message.errors %}
|
||||
<div class="text-danger">{{ form.message.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Send Message</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<h5>Project Info</h5>
|
||||
<p><strong>Django Version:</strong> 4.2.7</p>
|
||||
<p><strong>Purpose:</strong> VPS Deployment Testing</p>
|
||||
<p><strong>Features:</strong></p>
|
||||
<ul>
|
||||
<li>Contact Form</li>
|
||||
<li>Blog System</li>
|
||||
<li>Environment Configuration</li>
|
||||
<li>Production Ready</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
77
core/templates/core/home.html
Normal file
77
core/templates/core/home.html
Normal file
@ -0,0 +1,77 @@
|
||||
{% extends 'core/base.html' %}
|
||||
|
||||
{% block title %}Home - Django Demo Project{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="hero-section bg-primary text-white py-5">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<h1 class="display-4">Django Demo Project</h1>
|
||||
<p class="lead">A production-ready Django application template optimized for VPS hosting deployment.</p>
|
||||
<a href="{% url 'contact' %}" class="btn btn-light btn-lg">Get Started</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container py-5">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<h2>Features</h2>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Production Ready</h5>
|
||||
<p class="card-text">Configured with environment variables, security settings, and optimized for deployment.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Database Flexible</h5>
|
||||
<p class="card-text">Works with SQLite for development and PostgreSQL for production.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">VPS Optimized</h5>
|
||||
<p class="card-text">Includes deployment scripts and configuration for easy VPS hosting.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Minimal Dependencies</h5>
|
||||
<p class="card-text">Clean, lightweight setup with only essential packages.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<h3>Recent Blog Posts</h3>
|
||||
{% if blog_posts %}
|
||||
{% for post in blog_posts %}
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ post.title }}</h5>
|
||||
<p class="card-text">{{ post.content|truncatewords:20 }}</p>
|
||||
<small class="text-muted">{{ post.created_at|date:"M d, Y" }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<a href="{% url 'blog' %}" class="btn btn-outline-primary">View All Posts</a>
|
||||
{% else %}
|
||||
<p class="text-muted">No blog posts yet. Check back later!</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
3
core/tests.py
Normal file
3
core/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
9
core/urls.py
Normal file
9
core/urls.py
Normal file
@ -0,0 +1,9 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.home, name='home'),
|
||||
path('contact/', views.contact, name='contact'),
|
||||
path('blog/', views.blog, name='blog'),
|
||||
path('about/', views.about, name='about'),
|
||||
]
|
||||
31
core/views.py
Normal file
31
core/views.py
Normal file
@ -0,0 +1,31 @@
|
||||
from django.shortcuts import render, redirect
|
||||
from django.contrib import messages
|
||||
from .models import BlogPost
|
||||
from .forms import ContactForm
|
||||
|
||||
|
||||
def home(request):
|
||||
blog_posts = BlogPost.objects.filter(is_published=True)[:3]
|
||||
return render(request, 'core/home.html', {'blog_posts': blog_posts})
|
||||
|
||||
|
||||
def contact(request):
|
||||
if request.method == 'POST':
|
||||
form = ContactForm(request.POST)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, 'Thank you for your message! We will get back to you soon.')
|
||||
return redirect('contact')
|
||||
else:
|
||||
form = ContactForm()
|
||||
|
||||
return render(request, 'core/contact.html', {'form': form})
|
||||
|
||||
|
||||
def blog(request):
|
||||
posts = BlogPost.objects.filter(is_published=True)
|
||||
return render(request, 'core/blog.html', {'posts': posts})
|
||||
|
||||
|
||||
def about(request):
|
||||
return render(request, 'core/about.html')
|
||||
0
demo_project/__init__.py
Normal file
0
demo_project/__init__.py
Normal file
16
demo_project/asgi.py
Normal file
16
demo_project/asgi.py
Normal file
@ -0,0 +1,16 @@
|
||||
"""
|
||||
ASGI config for demo_project project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'demo_project.settings')
|
||||
|
||||
application = get_asgi_application()
|
||||
162
demo_project/settings.py
Normal file
162
demo_project/settings.py
Normal file
@ -0,0 +1,162 @@
|
||||
"""
|
||||
Django settings for demo_project project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 4.2.7.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.2/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/4.2/ref/settings/
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from decouple import config
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = config('SECRET_KEY', default='django-insecure-%%73_#43bfj4u2^usxt&(hmx$*r!&@w6z7!r-psjm%gu_5(s35')
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = config('DEBUG', default=True, cast=bool)
|
||||
|
||||
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='localhost,127.0.0.1', cast=lambda v: [s.strip() for s in v.split(',')])
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'core',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'whitenoise.middleware.WhiteNoiseMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'demo_project.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'demo_project.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
||||
|
||||
# Database configuration
|
||||
if config('DATABASE_URL', default=None):
|
||||
import dj_database_url
|
||||
DATABASES = {
|
||||
'default': dj_database_url.parse(config('DATABASE_URL'))
|
||||
}
|
||||
elif config('USE_POSTGRES', default=False, cast=bool):
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': config('DB_NAME', default='demo_db'),
|
||||
'USER': config('DB_USER', default='demo_user'),
|
||||
'PASSWORD': config('DB_PASSWORD', default=''),
|
||||
'HOST': config('DB_HOST', default='localhost'),
|
||||
'PORT': config('DB_PORT', default='5432'),
|
||||
}
|
||||
}
|
||||
else:
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
||||
STATICFILES_DIRS = []
|
||||
|
||||
# Whitenoise settings
|
||||
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
# Production security settings
|
||||
if not DEBUG:
|
||||
SECURE_BROWSER_XSS_FILTER = True
|
||||
SECURE_CONTENT_TYPE_NOSNIFF = True
|
||||
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
|
||||
SECURE_HSTS_SECONDS = 31536000
|
||||
SECURE_REDIRECT_EXEMPT = []
|
||||
SECURE_SSL_REDIRECT = config('SECURE_SSL_REDIRECT', default=False, cast=bool)
|
||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||
SESSION_COOKIE_SECURE = True
|
||||
CSRF_COOKIE_SECURE = True
|
||||
23
demo_project/urls.py
Normal file
23
demo_project/urls.py
Normal file
@ -0,0 +1,23 @@
|
||||
"""
|
||||
URL configuration for demo_project project.
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/4.2/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('', include('core.urls')),
|
||||
]
|
||||
16
demo_project/wsgi.py
Normal file
16
demo_project/wsgi.py
Normal file
@ -0,0 +1,16 @@
|
||||
"""
|
||||
WSGI config for demo_project project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'demo_project.settings')
|
||||
|
||||
application = get_wsgi_application()
|
||||
103
deploy/deploy.sh
Executable file
103
deploy/deploy.sh
Executable file
@ -0,0 +1,103 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Django Demo Project Deployment Script
|
||||
# Run this script on your VPS to deploy the application
|
||||
|
||||
set -e
|
||||
|
||||
echo "🚀 Starting Django Demo Project deployment..."
|
||||
|
||||
# Configuration
|
||||
PROJECT_DIR="/home/ubuntu/django-demo"
|
||||
SERVICE_NAME="django-demo"
|
||||
NGINX_SITE="django-demo"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Check if running as root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo -e "${RED}Please run this script as root (use sudo)${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${YELLOW}📦 Installing system packages...${NC}"
|
||||
apt update
|
||||
apt install -y python3 python3-venv python3-pip nginx postgresql postgresql-contrib
|
||||
|
||||
echo -e "${YELLOW}👤 Setting up project user and directories...${NC}"
|
||||
# Create project directory
|
||||
mkdir -p $PROJECT_DIR
|
||||
mkdir -p /var/log/django
|
||||
mkdir -p /var/run/gunicorn
|
||||
|
||||
# Set ownership
|
||||
chown -R www-data:www-data /var/log/django
|
||||
chown -R www-data:www-data /var/run/gunicorn
|
||||
|
||||
echo -e "${YELLOW}🐍 Setting up Python virtual environment...${NC}"
|
||||
cd $PROJECT_DIR
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
|
||||
echo -e "${YELLOW}📋 Installing Python packages...${NC}"
|
||||
pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
|
||||
echo -e "${YELLOW}⚙️ Configuring environment...${NC}"
|
||||
if [ ! -f ".env" ]; then
|
||||
echo "Creating .env file from example..."
|
||||
cp .env.example .env
|
||||
echo -e "${RED}⚠️ IMPORTANT: Edit .env file with your production settings!${NC}"
|
||||
echo " - Set a secure SECRET_KEY"
|
||||
echo " - Set DEBUG=False"
|
||||
echo " - Configure ALLOWED_HOSTS with your domain"
|
||||
echo " - Configure database settings if using PostgreSQL"
|
||||
fi
|
||||
|
||||
echo -e "${YELLOW}🗄️ Setting up database...${NC}"
|
||||
python manage.py collectstatic --noinput
|
||||
python manage.py migrate
|
||||
|
||||
echo -e "${YELLOW}👤 Creating Django superuser (optional)...${NC}"
|
||||
echo "Would you like to create a superuser? (y/N)"
|
||||
read -r response
|
||||
if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then
|
||||
python manage.py createsuperuser
|
||||
fi
|
||||
|
||||
echo -e "${YELLOW}🔧 Setting up systemd service...${NC}"
|
||||
cp deploy/systemd.service /etc/systemd/system/${SERVICE_NAME}.service
|
||||
systemctl daemon-reload
|
||||
systemctl enable $SERVICE_NAME
|
||||
|
||||
echo -e "${YELLOW}🌐 Setting up Nginx...${NC}"
|
||||
cp deploy/nginx.conf /etc/nginx/sites-available/$NGINX_SITE
|
||||
ln -sf /etc/nginx/sites-available/$NGINX_SITE /etc/nginx/sites-enabled/
|
||||
rm -f /etc/nginx/sites-enabled/default
|
||||
nginx -t
|
||||
|
||||
echo -e "${YELLOW}🔄 Starting services...${NC}"
|
||||
systemctl restart $SERVICE_NAME
|
||||
systemctl restart nginx
|
||||
|
||||
echo -e "${YELLOW}🔍 Checking service status...${NC}"
|
||||
systemctl is-active --quiet $SERVICE_NAME && echo -e "${GREEN}✅ Django service is running${NC}" || echo -e "${RED}❌ Django service failed${NC}"
|
||||
systemctl is-active --quiet nginx && echo -e "${GREEN}✅ Nginx is running${NC}" || echo -e "${RED}❌ Nginx failed${NC}"
|
||||
|
||||
echo -e "${GREEN}🎉 Deployment completed!${NC}"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo "1. Edit .env file with your production settings"
|
||||
echo "2. Update domain name in nginx.conf"
|
||||
echo "3. Set up SSL certificate (recommended: Let's Encrypt)"
|
||||
echo "4. Configure firewall (ufw) to allow HTTP/HTTPS traffic"
|
||||
echo ""
|
||||
echo "Useful commands:"
|
||||
echo " - Restart Django: sudo systemctl restart $SERVICE_NAME"
|
||||
echo " - View logs: sudo journalctl -u $SERVICE_NAME -f"
|
||||
echo " - Restart Nginx: sudo systemctl restart nginx"
|
||||
echo " - Check status: sudo systemctl status $SERVICE_NAME"
|
||||
35
deploy/gunicorn.conf.py
Normal file
35
deploy/gunicorn.conf.py
Normal file
@ -0,0 +1,35 @@
|
||||
"""Gunicorn configuration for production deployment"""
|
||||
|
||||
import multiprocessing
|
||||
import os
|
||||
|
||||
# Server socket
|
||||
bind = "127.0.0.1:8000"
|
||||
backlog = 2048
|
||||
|
||||
# Worker processes
|
||||
workers = multiprocessing.cpu_count() * 2 + 1
|
||||
worker_class = "sync"
|
||||
worker_connections = 1000
|
||||
timeout = 30
|
||||
keepalive = 2
|
||||
max_requests = 1000
|
||||
max_requests_jitter = 100
|
||||
|
||||
# Restart workers after this many requests, with up to max_requests_jitter additional requests
|
||||
preload_app = True
|
||||
|
||||
# Logging
|
||||
accesslog = "/var/log/django/access.log"
|
||||
errorlog = "/var/log/django/error.log"
|
||||
loglevel = "info"
|
||||
|
||||
# Process naming
|
||||
proc_name = "django_demo_project"
|
||||
|
||||
# Server mechanics
|
||||
daemon = False
|
||||
pidfile = "/var/run/gunicorn/django_demo.pid"
|
||||
user = "www-data"
|
||||
group = "www-data"
|
||||
tmp_upload_dir = None
|
||||
66
deploy/nginx.conf
Normal file
66
deploy/nginx.conf
Normal file
@ -0,0 +1,66 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name yourdomain.com www.yourdomain.com;
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
||||
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
|
||||
|
||||
# Gzip compression
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 1024;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_types
|
||||
text/plain
|
||||
text/css
|
||||
text/xml
|
||||
text/javascript
|
||||
application/json
|
||||
application/javascript
|
||||
application/xml+rss
|
||||
application/atom+xml
|
||||
image/svg+xml;
|
||||
|
||||
# Static files
|
||||
location /static/ {
|
||||
alias /home/ubuntu/django-demo/staticfiles/;
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# Media files (if you add file uploads later)
|
||||
location /media/ {
|
||||
alias /home/ubuntu/django-demo/media/;
|
||||
expires 1y;
|
||||
add_header Cache-Control "public";
|
||||
}
|
||||
|
||||
# Django application
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_redirect off;
|
||||
|
||||
# Timeouts
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
}
|
||||
|
||||
# Block access to sensitive files
|
||||
location ~* /\.(?!well-known\/) {
|
||||
deny all;
|
||||
}
|
||||
|
||||
location ~* /(requirements\.txt|\.env|deploy/) {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
22
deploy/systemd.service
Normal file
22
deploy/systemd.service
Normal file
@ -0,0 +1,22 @@
|
||||
[Unit]
|
||||
Description=Django Demo Project Gunicorn daemon
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
User=www-data
|
||||
Group=www-data
|
||||
RuntimeDirectory=gunicorn
|
||||
WorkingDirectory=/home/ubuntu/django-demo
|
||||
ExecStart=/home/ubuntu/django-demo/venv/bin/gunicorn --config /home/ubuntu/django-demo/deploy/gunicorn.conf.py demo_project.wsgi:application
|
||||
ExecReload=/bin/kill -s HUP $MAINPID
|
||||
KillMode=mixed
|
||||
TimeoutStopSec=5
|
||||
PrivateTmp=true
|
||||
|
||||
# Environment variables
|
||||
Environment=DJANGO_SETTINGS_MODULE=demo_project.settings
|
||||
EnvironmentFile=/home/ubuntu/django-demo/.env
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
52
deploy/update.sh
Executable file
52
deploy/update.sh
Executable file
@ -0,0 +1,52 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Django Demo Project Update Script
|
||||
# Run this script to deploy updates to your running application
|
||||
|
||||
set -e
|
||||
|
||||
echo "🔄 Starting application update..."
|
||||
|
||||
# Configuration
|
||||
PROJECT_DIR="/home/ubuntu/django-demo"
|
||||
SERVICE_NAME="django-demo"
|
||||
|
||||
# Colors for output
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Check if running as root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Please run this script as root (use sudo)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${YELLOW}📁 Navigating to project directory...${NC}"
|
||||
cd $PROJECT_DIR
|
||||
|
||||
echo -e "${YELLOW}🐍 Activating virtual environment...${NC}"
|
||||
source venv/bin/activate
|
||||
|
||||
echo -e "${YELLOW}📦 Installing/updating dependencies...${NC}"
|
||||
pip install -r requirements.txt
|
||||
|
||||
echo -e "${YELLOW}🗄️ Running database migrations...${NC}"
|
||||
python manage.py migrate
|
||||
|
||||
echo -e "${YELLOW}📄 Collecting static files...${NC}"
|
||||
python manage.py collectstatic --noinput
|
||||
|
||||
echo -e "${YELLOW}🔄 Restarting Django service...${NC}"
|
||||
systemctl restart $SERVICE_NAME
|
||||
|
||||
echo -e "${YELLOW}🔍 Checking service status...${NC}"
|
||||
if systemctl is-active --quiet $SERVICE_NAME; then
|
||||
echo -e "${GREEN}✅ Service is running successfully${NC}"
|
||||
else
|
||||
echo "❌ Service failed to start. Check logs:"
|
||||
echo "sudo journalctl -u $SERVICE_NAME -f"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✅ Update completed successfully!${NC}"
|
||||
22
manage.py
Executable file
22
manage.py
Executable file
@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'demo_project.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
6
requirements-dev.txt
Normal file
6
requirements-dev.txt
Normal file
@ -0,0 +1,6 @@
|
||||
# Development requirements
|
||||
-r requirements.txt
|
||||
|
||||
# Development tools (optional)
|
||||
# django-debug-toolbar==4.2.0
|
||||
# django-extensions==3.2.3
|
||||
13
requirements.txt
Normal file
13
requirements.txt
Normal file
@ -0,0 +1,13 @@
|
||||
# Core Django
|
||||
Django==4.2.7
|
||||
|
||||
# Configuration & Environment
|
||||
python-decouple==3.8
|
||||
dj-database-url==2.1.0
|
||||
|
||||
# Production Server & Static Files
|
||||
gunicorn==21.2.0
|
||||
whitenoise==6.6.0
|
||||
|
||||
# Database (PostgreSQL)
|
||||
psycopg2-binary==2.9.7
|
||||
9
requirements_full.txt
Normal file
9
requirements_full.txt
Normal file
@ -0,0 +1,9 @@
|
||||
asgiref==3.9.1
|
||||
dj-database-url==2.1.0
|
||||
Django==4.2.7
|
||||
gunicorn==21.2.0
|
||||
packaging==25.0
|
||||
python-decouple==3.8
|
||||
sqlparse==0.5.3
|
||||
typing_extensions==4.15.0
|
||||
whitenoise==6.6.0
|
||||
Loading…
Reference in New Issue
Block a user