commit 2f3f1ea867a85459f50d1a13842b8028ad71d9cc Author: thecyberlearn Date: Fri Aug 29 21:50:08 2025 +0530 Initial commit: Django demo project for VPS deployment 🎉 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d69b6d2 --- /dev/null +++ b/.env.example @@ -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 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4690ccc --- /dev/null +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/DEPLOYMENT_GUIDE.md b/DEPLOYMENT_GUIDE.md new file mode 100644 index 0000000..9a8cebf --- /dev/null +++ b/DEPLOYMENT_GUIDE.md @@ -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 +``` \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9d8657d --- /dev/null +++ b/README.md @@ -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 + 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. \ No newline at end of file diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/admin.py b/core/admin.py new file mode 100644 index 0000000..e9d3fc7 --- /dev/null +++ b/core/admin.py @@ -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'] diff --git a/core/apps.py b/core/apps.py new file mode 100644 index 0000000..8115ae6 --- /dev/null +++ b/core/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CoreConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'core' diff --git a/core/forms.py b/core/forms.py new file mode 100644 index 0000000..78deebf --- /dev/null +++ b/core/forms.py @@ -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...' + }), + } \ No newline at end of file diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py new file mode 100644 index 0000000..587cf58 --- /dev/null +++ b/core/migrations/0001_initial.py @@ -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'], + }, + ), + ] diff --git a/core/migrations/__init__.py b/core/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/models.py b/core/models.py new file mode 100644 index 0000000..8a68930 --- /dev/null +++ b/core/models.py @@ -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'] diff --git a/core/static/core/css/style.css b/core/static/core/css/style.css new file mode 100644 index 0000000..c892000 --- /dev/null +++ b/core/static/core/css/style.css @@ -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; +} \ No newline at end of file diff --git a/core/static/core/js/main.js b/core/static/core/js/main.js new file mode 100644 index 0000000..eef10fc --- /dev/null +++ b/core/static/core/js/main.js @@ -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' + }); + } + }); + }); +}); \ No newline at end of file diff --git a/core/templates/core/about.html b/core/templates/core/about.html new file mode 100644 index 0000000..c5e371e --- /dev/null +++ b/core/templates/core/about.html @@ -0,0 +1,63 @@ +{% extends 'core/base.html' %} + +{% block title %}About - Django Demo Project{% endblock %} + +{% block content %} +
+
+
+

About This Project

+ +
+
+
Purpose
+

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.

+
+
+ +
+
+
Features
+
    +
  • ✓ Minimal dependencies for fast deployment
  • +
  • ✓ Environment-based configuration
  • +
  • ✓ SQLite development / PostgreSQL production
  • +
  • ✓ Static files handling with WhiteNoise
  • +
  • ✓ Contact form functionality
  • +
  • ✓ Simple blog system
  • +
  • ✓ Bootstrap-based responsive UI
  • +
  • ✓ Production security settings
  • +
+
+
+ +
+
+
Tech Stack
+
+
+
    +
  • Backend: Django 4.2.7
  • +
  • Database: SQLite/PostgreSQL
  • +
  • Server: Gunicorn
  • +
+
+
+
    +
  • Frontend: Bootstrap 5
  • +
  • Static Files: WhiteNoise
  • +
  • Config: python-decouple
  • +
+
+
+
+
+ + +
+
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/base.html b/core/templates/core/base.html new file mode 100644 index 0000000..4db926e --- /dev/null +++ b/core/templates/core/base.html @@ -0,0 +1,63 @@ + + + + + + {% block title %}Django Demo Project{% endblock %} + + {% load static %} + + + + + +
+ {% if messages %} +
+ {% for message in messages %} + + {% endfor %} +
+ {% endif %} + + {% block content %} + {% endblock %} +
+ +
+
+

© 2024 Django Demo Project. Built for VPS deployment testing.

+
+
+ + + {% load static %} + + + \ No newline at end of file diff --git a/core/templates/core/blog.html b/core/templates/core/blog.html new file mode 100644 index 0000000..bdbf728 --- /dev/null +++ b/core/templates/core/blog.html @@ -0,0 +1,31 @@ +{% extends 'core/base.html' %} + +{% block title %}Blog - Django Demo Project{% endblock %} + +{% block content %} +
+

Blog

+ + {% if posts %} +
+ {% for post in posts %} +
+
+
+
{{ post.title }}
+

{{ post.content|truncatewords:30 }}

+ Published on {{ post.created_at|date:"F d, Y" }} +
+
+
+ {% endfor %} +
+ {% else %} +
+

No blog posts yet

+

Blog posts will appear here once they're created in the admin panel.

+ Back to Home +
+ {% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/contact.html b/core/templates/core/contact.html new file mode 100644 index 0000000..3cfa6a8 --- /dev/null +++ b/core/templates/core/contact.html @@ -0,0 +1,68 @@ +{% extends 'core/base.html' %} + +{% block title %}Contact - Django Demo Project{% endblock %} + +{% block content %} +
+
+
+

Contact Us

+

Have questions about this Django demo project? Get in touch!

+ +
+
+
+ {% csrf_token %} +
+ + {{ form.name }} + {% if form.name.errors %} +
{{ form.name.errors }}
+ {% endif %} +
+ +
+ + {{ form.email }} + {% if form.email.errors %} +
{{ form.email.errors }}
+ {% endif %} +
+ +
+ + {{ form.subject }} + {% if form.subject.errors %} +
{{ form.subject.errors }}
+ {% endif %} +
+ +
+ + {{ form.message }} + {% if form.message.errors %} +
{{ form.message.errors }}
+ {% endif %} +
+ + +
+
+ +
+
Project Info
+

Django Version: 4.2.7

+

Purpose: VPS Deployment Testing

+

Features:

+
    +
  • Contact Form
  • +
  • Blog System
  • +
  • Environment Configuration
  • +
  • Production Ready
  • +
+
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/home.html b/core/templates/core/home.html new file mode 100644 index 0000000..b194ad2 --- /dev/null +++ b/core/templates/core/home.html @@ -0,0 +1,77 @@ +{% extends 'core/base.html' %} + +{% block title %}Home - Django Demo Project{% endblock %} + +{% block content %} +
+
+
+
+

Django Demo Project

+

A production-ready Django application template optimized for VPS hosting deployment.

+ Get Started +
+
+
+
+ +
+
+
+

Features

+
+
+
+
+
Production Ready
+

Configured with environment variables, security settings, and optimized for deployment.

+
+
+
+
+
+
+
Database Flexible
+

Works with SQLite for development and PostgreSQL for production.

+
+
+
+
+
+
+
VPS Optimized
+

Includes deployment scripts and configuration for easy VPS hosting.

+
+
+
+
+
+
+
Minimal Dependencies
+

Clean, lightweight setup with only essential packages.

+
+
+
+
+
+ +
+

Recent Blog Posts

+ {% if blog_posts %} + {% for post in blog_posts %} +
+
+
{{ post.title }}
+

{{ post.content|truncatewords:20 }}

+ {{ post.created_at|date:"M d, Y" }} +
+
+ {% endfor %} + View All Posts + {% else %} +

No blog posts yet. Check back later!

+ {% endif %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/core/tests.py b/core/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/core/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/core/urls.py b/core/urls.py new file mode 100644 index 0000000..b9ceec5 --- /dev/null +++ b/core/urls.py @@ -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'), +] \ No newline at end of file diff --git a/core/views.py b/core/views.py new file mode 100644 index 0000000..832b055 --- /dev/null +++ b/core/views.py @@ -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') diff --git a/demo_project/__init__.py b/demo_project/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/demo_project/asgi.py b/demo_project/asgi.py new file mode 100644 index 0000000..ddea556 --- /dev/null +++ b/demo_project/asgi.py @@ -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() diff --git a/demo_project/settings.py b/demo_project/settings.py new file mode 100644 index 0000000..e240d36 --- /dev/null +++ b/demo_project/settings.py @@ -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 diff --git a/demo_project/urls.py b/demo_project/urls.py new file mode 100644 index 0000000..27e033e --- /dev/null +++ b/demo_project/urls.py @@ -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')), +] diff --git a/demo_project/wsgi.py b/demo_project/wsgi.py new file mode 100644 index 0000000..fe16259 --- /dev/null +++ b/demo_project/wsgi.py @@ -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() diff --git a/deploy/deploy.sh b/deploy/deploy.sh new file mode 100755 index 0000000..a2b58eb --- /dev/null +++ b/deploy/deploy.sh @@ -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" \ No newline at end of file diff --git a/deploy/gunicorn.conf.py b/deploy/gunicorn.conf.py new file mode 100644 index 0000000..781abe3 --- /dev/null +++ b/deploy/gunicorn.conf.py @@ -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 \ No newline at end of file diff --git a/deploy/nginx.conf b/deploy/nginx.conf new file mode 100644 index 0000000..d7455f3 --- /dev/null +++ b/deploy/nginx.conf @@ -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; + } +} \ No newline at end of file diff --git a/deploy/systemd.service b/deploy/systemd.service new file mode 100644 index 0000000..990d23e --- /dev/null +++ b/deploy/systemd.service @@ -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 \ No newline at end of file diff --git a/deploy/update.sh b/deploy/update.sh new file mode 100755 index 0000000..4e1fce4 --- /dev/null +++ b/deploy/update.sh @@ -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}" \ No newline at end of file diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..8da2b48 --- /dev/null +++ b/manage.py @@ -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() diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..8a009fc --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,6 @@ +# Development requirements +-r requirements.txt + +# Development tools (optional) +# django-debug-toolbar==4.2.0 +# django-extensions==3.2.3 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..301e9de --- /dev/null +++ b/requirements.txt @@ -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 \ No newline at end of file diff --git a/requirements_full.txt b/requirements_full.txt new file mode 100644 index 0000000..891ddd8 --- /dev/null +++ b/requirements_full.txt @@ -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