update
This commit is contained in:
213
backEnd/blog/README.md
Normal file
213
backEnd/blog/README.md
Normal file
@@ -0,0 +1,213 @@
|
||||
# Blog API Documentation
|
||||
|
||||
This document provides information about the Blog API implementation for the GNX Software Solutions website.
|
||||
|
||||
## Overview
|
||||
|
||||
The Blog API is a RESTful API built with Django REST Framework that provides endpoints for managing blog posts, categories, authors, tags, and comments.
|
||||
|
||||
## Backend Setup
|
||||
|
||||
### Models
|
||||
|
||||
The blog app includes the following models:
|
||||
|
||||
1. **BlogAuthor** - Stores information about blog post authors
|
||||
2. **BlogCategory** - Categories for organizing blog posts
|
||||
3. **BlogTag** - Tags for additional post categorization
|
||||
4. **BlogPost** - The main blog post model
|
||||
5. **BlogComment** - Comments on blog posts (with moderation support)
|
||||
|
||||
### API Endpoints
|
||||
|
||||
Base URL: `/api/blog/`
|
||||
|
||||
#### Blog Posts
|
||||
|
||||
- `GET /api/blog/posts/` - List all blog posts (with pagination and filtering)
|
||||
- Query parameters:
|
||||
- `category` - Filter by category slug
|
||||
- `tag` - Filter by tag slug
|
||||
- `author` - Filter by author ID
|
||||
- `search` - Search in title, content, and excerpt
|
||||
- `featured` - Filter featured posts
|
||||
- `ordering` - Sort by fields (e.g., `-published_at`, `views_count`)
|
||||
- `page` - Page number
|
||||
- `page_size` - Items per page (default: 9)
|
||||
|
||||
- `GET /api/blog/posts/{slug}/` - Get a single blog post by slug
|
||||
- `GET /api/blog/posts/featured/` - Get featured blog posts
|
||||
- `GET /api/blog/posts/latest/?limit=5` - Get latest blog posts
|
||||
- `GET /api/blog/posts/popular/?limit=5` - Get popular posts by views
|
||||
- `GET /api/blog/posts/{id}/related/` - Get related posts
|
||||
|
||||
#### Categories
|
||||
|
||||
- `GET /api/blog/categories/` - List all categories
|
||||
- `GET /api/blog/categories/{slug}/` - Get a single category by slug
|
||||
- `GET /api/blog/categories/with_posts/` - Get categories that have published posts
|
||||
|
||||
#### Authors
|
||||
|
||||
- `GET /api/blog/authors/` - List all authors
|
||||
- `GET /api/blog/authors/{id}/` - Get a single author
|
||||
- `GET /api/blog/authors/{id}/posts/` - Get all posts by an author
|
||||
|
||||
#### Tags
|
||||
|
||||
- `GET /api/blog/tags/` - List all tags
|
||||
- `GET /api/blog/tags/{slug}/` - Get a single tag by slug
|
||||
- `GET /api/blog/tags/{slug}/posts/` - Get all posts with a specific tag
|
||||
|
||||
#### Comments
|
||||
|
||||
- `GET /api/blog/comments/?post={post_id}` - Get comments for a post
|
||||
- `POST /api/blog/comments/` - Create a new comment (requires moderation)
|
||||
|
||||
### Management Commands
|
||||
|
||||
#### Populate Blog Data
|
||||
|
||||
To populate the database with sample blog data:
|
||||
|
||||
```bash
|
||||
python manage.py populate_blog
|
||||
```
|
||||
|
||||
This command creates:
|
||||
- 4 sample authors
|
||||
- 6 blog categories
|
||||
- 16 tags
|
||||
- 8 sample blog posts with full content
|
||||
|
||||
## Frontend Integration
|
||||
|
||||
### API Service
|
||||
|
||||
Location: `/lib/api/blogService.ts`
|
||||
|
||||
The service provides functions for:
|
||||
- Fetching blog posts (with filtering)
|
||||
- Getting single posts by slug
|
||||
- Fetching categories, tags, and authors
|
||||
- Getting featured, latest, and popular posts
|
||||
- Managing comments
|
||||
|
||||
### Custom Hooks
|
||||
|
||||
Location: `/lib/hooks/useBlog.ts`
|
||||
|
||||
Available hooks:
|
||||
- `useBlogPosts(params)` - Fetch paginated list of posts
|
||||
- `useBlogPost(slug)` - Fetch a single post
|
||||
- `useFeaturedPosts()` - Fetch featured posts
|
||||
- `useLatestPosts(limit)` - Fetch latest posts
|
||||
- `usePopularPosts(limit)` - Fetch popular posts
|
||||
- `useBlogCategories()` - Fetch all categories
|
||||
- `useBlogTags()` - Fetch all tags
|
||||
- `useBlogAuthors()` - Fetch all authors
|
||||
|
||||
### Components
|
||||
|
||||
The following components have been updated to use the API:
|
||||
|
||||
1. **PostFilterButtons** - Fetches categories from API
|
||||
2. **PostFilterItems** - Fetches and filters blog posts by category
|
||||
3. **BlogSingle** - Fetches individual post by slug from URL params
|
||||
4. **LatestPost** - Fetches latest posts for the slider
|
||||
|
||||
### Usage Examples
|
||||
|
||||
#### Fetching All Posts
|
||||
|
||||
```typescript
|
||||
const { posts, loading, error, pagination } = useBlogPosts({
|
||||
category: 'enterprise-software',
|
||||
page_size: 9
|
||||
});
|
||||
```
|
||||
|
||||
#### Fetching a Single Post
|
||||
|
||||
```typescript
|
||||
const { post, loading, error } = useBlogPost('api-first-approach-to-system-integration');
|
||||
```
|
||||
|
||||
#### Fetching Latest Posts
|
||||
|
||||
```typescript
|
||||
const { posts, loading, error } = useLatestPosts(5);
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
### Backend Features
|
||||
|
||||
1. **Pagination** - All list endpoints support pagination
|
||||
2. **Filtering** - Filter posts by category, tag, author, and search
|
||||
3. **Ordering** - Sort posts by date, views, etc.
|
||||
4. **View Tracking** - Automatically increment view count when a post is viewed
|
||||
5. **Related Posts** - Automatically suggest related posts from the same category
|
||||
6. **Comment Moderation** - Comments require approval before being visible
|
||||
7. **SEO Support** - Meta descriptions and keywords for each post
|
||||
8. **Reading Time** - Estimated reading time for posts
|
||||
9. **Tags System** - Flexible tagging for better categorization
|
||||
|
||||
### Frontend Features
|
||||
|
||||
1. **Real-time Data** - All data fetched from API
|
||||
2. **Loading States** - Proper loading indicators
|
||||
3. **Error Handling** - Graceful error handling with fallbacks
|
||||
4. **Image Optimization** - Uses Next.js Image component
|
||||
5. **Responsive Design** - Mobile-friendly layouts
|
||||
6. **Category Filtering** - Filter posts by category with smooth animations
|
||||
7. **Social Sharing** - Share posts on social media
|
||||
8. **Related Posts** - Automatically shows related posts
|
||||
9. **SEO Friendly** - Proper meta tags and structured data
|
||||
|
||||
## Sample Data
|
||||
|
||||
The populated sample data includes:
|
||||
|
||||
### Categories
|
||||
- Enterprise Software
|
||||
- Digital Transformation
|
||||
- System Integration
|
||||
- Cloud Solutions
|
||||
- Security
|
||||
- API Development
|
||||
|
||||
### Sample Post Topics
|
||||
1. The Future of Enterprise Software Architecture
|
||||
2. Digital Transformation Strategies for Large Enterprises
|
||||
3. API-First Approach to System Integration
|
||||
4. Cloud Migration Best Practices for Enterprise
|
||||
5. Enterprise Security in the Digital Age
|
||||
6. Building Scalable API Architectures
|
||||
7. Microservices Architecture for Enterprise Applications
|
||||
8. Data Analytics and Business Intelligence Solutions
|
||||
|
||||
## Notes
|
||||
|
||||
- All blog posts support HTML content
|
||||
- Images can be uploaded or linked via URL
|
||||
- Posts can be marked as featured
|
||||
- Comments require moderation before being visible
|
||||
- The API respects published status (unpublished posts are not returned)
|
||||
- View counts are automatically tracked
|
||||
- Related posts are determined by category
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Potential improvements:
|
||||
1. Add full-text search using Elasticsearch
|
||||
2. Implement post series/collections
|
||||
3. Add newsletter subscription functionality
|
||||
4. Implement post scheduling
|
||||
5. Add analytics dashboard
|
||||
6. Support for multiple authors per post
|
||||
7. Rich text editor in admin
|
||||
8. Image upload and management
|
||||
9. Post translations/i18n support
|
||||
10. RSS feed generation
|
||||
|
||||
0
backEnd/blog/__init__.py
Normal file
0
backEnd/blog/__init__.py
Normal file
BIN
backEnd/blog/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
backEnd/blog/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backEnd/blog/__pycache__/admin.cpython-312.pyc
Normal file
BIN
backEnd/blog/__pycache__/admin.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backEnd/blog/__pycache__/apps.cpython-312.pyc
Normal file
BIN
backEnd/blog/__pycache__/apps.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backEnd/blog/__pycache__/models.cpython-312.pyc
Normal file
BIN
backEnd/blog/__pycache__/models.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backEnd/blog/__pycache__/serializers.cpython-312.pyc
Normal file
BIN
backEnd/blog/__pycache__/serializers.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backEnd/blog/__pycache__/urls.cpython-312.pyc
Normal file
BIN
backEnd/blog/__pycache__/urls.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backEnd/blog/__pycache__/views.cpython-312.pyc
Normal file
BIN
backEnd/blog/__pycache__/views.cpython-312.pyc
Normal file
Binary file not shown.
98
backEnd/blog/admin.py
Normal file
98
backEnd/blog/admin.py
Normal file
@@ -0,0 +1,98 @@
|
||||
from django.contrib import admin
|
||||
from .models import BlogPost, BlogCategory, BlogAuthor, BlogTag, BlogComment
|
||||
|
||||
|
||||
@admin.register(BlogAuthor)
|
||||
class BlogAuthorAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'email', 'is_active', 'created_at']
|
||||
list_filter = ['is_active', 'created_at']
|
||||
search_fields = ['name', 'email']
|
||||
ordering = ['name']
|
||||
|
||||
|
||||
@admin.register(BlogCategory)
|
||||
class BlogCategoryAdmin(admin.ModelAdmin):
|
||||
list_display = ['title', 'slug', 'display_order', 'is_active', 'posts_count']
|
||||
list_filter = ['is_active']
|
||||
search_fields = ['title', 'slug']
|
||||
prepopulated_fields = {'slug': ('title',)}
|
||||
ordering = ['display_order', 'title']
|
||||
|
||||
def posts_count(self, obj):
|
||||
return obj.posts.count()
|
||||
posts_count.short_description = 'Posts Count'
|
||||
|
||||
|
||||
@admin.register(BlogTag)
|
||||
class BlogTagAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'slug', 'is_active', 'posts_count']
|
||||
list_filter = ['is_active']
|
||||
search_fields = ['name', 'slug']
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
ordering = ['name']
|
||||
|
||||
def posts_count(self, obj):
|
||||
return obj.posts.count()
|
||||
posts_count.short_description = 'Posts Count'
|
||||
|
||||
|
||||
@admin.register(BlogPost)
|
||||
class BlogPostAdmin(admin.ModelAdmin):
|
||||
list_display = [
|
||||
'title', 'author', 'category', 'published',
|
||||
'featured', 'views_count', 'published_at'
|
||||
]
|
||||
list_filter = [
|
||||
'published', 'featured', 'category',
|
||||
'author', 'published_at', 'created_at'
|
||||
]
|
||||
search_fields = ['title', 'content', 'excerpt']
|
||||
prepopulated_fields = {'slug': ('title',)}
|
||||
filter_horizontal = ['tags']
|
||||
date_hierarchy = 'published_at'
|
||||
ordering = ['-published_at', '-created_at']
|
||||
|
||||
fieldsets = (
|
||||
('Basic Information', {
|
||||
'fields': ('title', 'slug', 'author', 'category')
|
||||
}),
|
||||
('Content', {
|
||||
'fields': ('excerpt', 'content')
|
||||
}),
|
||||
('Images', {
|
||||
'fields': ('thumbnail', 'thumbnail_url', 'featured_image', 'featured_image_url')
|
||||
}),
|
||||
('Categorization', {
|
||||
'fields': ('tags',)
|
||||
}),
|
||||
('SEO', {
|
||||
'fields': ('meta_description', 'meta_keywords'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
('Status & Visibility', {
|
||||
'fields': ('published', 'featured', 'reading_time', 'published_at')
|
||||
}),
|
||||
('Statistics', {
|
||||
'fields': ('views_count',),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
)
|
||||
|
||||
readonly_fields = ['views_count']
|
||||
|
||||
|
||||
@admin.register(BlogComment)
|
||||
class BlogCommentAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'post', 'is_approved', 'created_at']
|
||||
list_filter = ['is_approved', 'created_at']
|
||||
search_fields = ['name', 'email', 'content', 'post__title']
|
||||
ordering = ['-created_at']
|
||||
actions = ['approve_comments', 'disapprove_comments']
|
||||
|
||||
def approve_comments(self, request, queryset):
|
||||
queryset.update(is_approved=True)
|
||||
approve_comments.short_description = "Approve selected comments"
|
||||
|
||||
def disapprove_comments(self, request, queryset):
|
||||
queryset.update(is_approved=False)
|
||||
disapprove_comments.short_description = "Disapprove selected comments"
|
||||
7
backEnd/blog/apps.py
Normal file
7
backEnd/blog/apps.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class BlogConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'blog'
|
||||
verbose_name = 'Blog Management'
|
||||
0
backEnd/blog/management/__init__.py
Normal file
0
backEnd/blog/management/__init__.py
Normal file
BIN
backEnd/blog/management/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
backEnd/blog/management/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
0
backEnd/blog/management/commands/__init__.py
Normal file
0
backEnd/blog/management/commands/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
414
backEnd/blog/management/commands/populate_blog.py
Normal file
414
backEnd/blog/management/commands/populate_blog.py
Normal file
@@ -0,0 +1,414 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from blog.models import BlogAuthor, BlogCategory, BlogTag, BlogPost
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Populate database with sample blog data'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
self.stdout.write(self.style.SUCCESS('Starting to populate blog data...'))
|
||||
|
||||
# Clear existing data
|
||||
BlogPost.objects.all().delete()
|
||||
BlogTag.objects.all().delete()
|
||||
BlogCategory.objects.all().delete()
|
||||
BlogAuthor.objects.all().delete()
|
||||
|
||||
# Create Authors
|
||||
authors_data = [
|
||||
{
|
||||
'name': 'Sarah Johnson',
|
||||
'email': 'sarah@gnxsoft.com',
|
||||
'bio': 'Senior Technology Consultant with 15+ years in enterprise solutions'
|
||||
},
|
||||
{
|
||||
'name': 'Michael Chen',
|
||||
'email': 'michael@gnxsoft.com',
|
||||
'bio': 'Cloud Architecture Specialist and DevOps Expert'
|
||||
},
|
||||
{
|
||||
'name': 'Emily Rodriguez',
|
||||
'email': 'emily@gnxsoft.com',
|
||||
'bio': 'API Integration and System Integration Lead'
|
||||
},
|
||||
{
|
||||
'name': 'David Thompson',
|
||||
'email': 'david@gnxsoft.com',
|
||||
'bio': 'Digital Transformation and Business Intelligence Consultant'
|
||||
}
|
||||
]
|
||||
|
||||
authors = {}
|
||||
for author_data in authors_data:
|
||||
author = BlogAuthor.objects.create(**author_data)
|
||||
authors[author.name] = author
|
||||
self.stdout.write(f'Created author: {author.name}')
|
||||
|
||||
# Create Categories
|
||||
categories_data = [
|
||||
{
|
||||
'title': 'Enterprise Software',
|
||||
'slug': 'enterprise-software',
|
||||
'description': 'Articles about enterprise software development and architecture',
|
||||
'display_order': 1
|
||||
},
|
||||
{
|
||||
'title': 'Digital Transformation',
|
||||
'slug': 'digital-transformation',
|
||||
'description': 'Insights on digital transformation strategies',
|
||||
'display_order': 2
|
||||
},
|
||||
{
|
||||
'title': 'System Integration',
|
||||
'slug': 'system-integration',
|
||||
'description': 'Best practices for system and API integration',
|
||||
'display_order': 3
|
||||
},
|
||||
{
|
||||
'title': 'Cloud Solutions',
|
||||
'slug': 'cloud-solutions',
|
||||
'description': 'Cloud computing and migration strategies',
|
||||
'display_order': 4
|
||||
},
|
||||
{
|
||||
'title': 'Security',
|
||||
'slug': 'security',
|
||||
'description': 'Enterprise security and cybersecurity topics',
|
||||
'display_order': 5
|
||||
},
|
||||
{
|
||||
'title': 'API Development',
|
||||
'slug': 'api-development',
|
||||
'description': 'API design, development, and management',
|
||||
'display_order': 6
|
||||
}
|
||||
]
|
||||
|
||||
categories = {}
|
||||
for cat_data in categories_data:
|
||||
category = BlogCategory.objects.create(**cat_data)
|
||||
categories[category.slug] = category
|
||||
self.stdout.write(f'Created category: {category.title}')
|
||||
|
||||
# Create Tags
|
||||
tags_data = [
|
||||
'API', 'REST', 'GraphQL', 'Microservices', 'Cloud', 'AWS', 'Azure',
|
||||
'Security', 'DevOps', 'CI/CD', 'Docker', 'Kubernetes',
|
||||
'Enterprise', 'Integration', 'Architecture', 'Best Practices'
|
||||
]
|
||||
|
||||
tags = {}
|
||||
for tag_name in tags_data:
|
||||
tag = BlogTag.objects.create(name=tag_name)
|
||||
tags[tag_name] = tag
|
||||
self.stdout.write(f'Created tag: {tag.name}')
|
||||
|
||||
# Create Blog Posts
|
||||
posts_data = [
|
||||
{
|
||||
'title': 'The Future of Enterprise Software Architecture',
|
||||
'content': '''
|
||||
<h2>Introduction</h2>
|
||||
<p>Enterprise software architecture is evolving rapidly. In this comprehensive guide, we explore the latest trends and best practices that are shaping the future of enterprise systems.</p>
|
||||
|
||||
<h3>Microservices and Modularity</h3>
|
||||
<p>The shift towards microservices architecture has revolutionized how we build enterprise applications. By breaking down monolithic applications into smaller, independent services, organizations can achieve greater flexibility, scalability, and maintainability.</p>
|
||||
|
||||
<h3>Cloud-Native Development</h3>
|
||||
<p>Cloud-native architecture is becoming the standard for modern enterprise applications. This approach leverages cloud computing capabilities to build and run scalable applications in dynamic environments.</p>
|
||||
|
||||
<h3>API-First Design</h3>
|
||||
<p>API-first development ensures that applications are built with integration in mind from the start. This approach facilitates better communication between services and makes it easier to integrate with third-party systems.</p>
|
||||
|
||||
<h2>Best Practices</h2>
|
||||
<ul>
|
||||
<li>Design for scalability from the ground up</li>
|
||||
<li>Implement robust security measures at every layer</li>
|
||||
<li>Use containerization for consistent deployment</li>
|
||||
<li>Adopt continuous integration and deployment practices</li>
|
||||
<li>Monitor and optimize performance continuously</li>
|
||||
</ul>
|
||||
|
||||
<h2>Conclusion</h2>
|
||||
<p>The future of enterprise software architecture lies in flexibility, scalability, and cloud-native approaches. Organizations that embrace these principles will be better positioned to adapt to changing business needs.</p>
|
||||
''',
|
||||
'excerpt': 'Exploring the latest trends in enterprise software architecture, from microservices to cloud-native development.',
|
||||
'author': authors['Sarah Johnson'],
|
||||
'category': categories['enterprise-software'],
|
||||
'tags': ['Enterprise', 'Architecture', 'Microservices', 'Cloud'],
|
||||
'thumbnail_url': '/images/blog/one.png',
|
||||
'featured': True,
|
||||
'reading_time': 8,
|
||||
'days_ago': 5
|
||||
},
|
||||
{
|
||||
'title': 'Digital Transformation Strategies for Large Enterprises',
|
||||
'content': '''
|
||||
<h2>Understanding Digital Transformation</h2>
|
||||
<p>Digital transformation is more than just adopting new technologies—it's about fundamentally changing how your organization operates and delivers value to customers.</p>
|
||||
|
||||
<h3>Key Components of Successful Transformation</h3>
|
||||
<p>A successful digital transformation strategy must address technology, processes, and culture simultaneously. Organizations that focus on only one aspect often struggle to achieve their goals.</p>
|
||||
|
||||
<h3>Technology Stack Modernization</h3>
|
||||
<p>Legacy systems can hold organizations back. Modernizing your technology stack is often the first step in digital transformation, enabling new capabilities and improving efficiency.</p>
|
||||
|
||||
<h2>Implementation Strategy</h2>
|
||||
<ol>
|
||||
<li>Assess current state and define vision</li>
|
||||
<li>Identify quick wins and long-term goals</li>
|
||||
<li>Build cross-functional transformation teams</li>
|
||||
<li>Implement in phases with measurable milestones</li>
|
||||
<li>Continuously gather feedback and adjust</li>
|
||||
</ol>
|
||||
|
||||
<h2>Overcoming Challenges</h2>
|
||||
<p>Common challenges include resistance to change, budget constraints, and technical debt. Success requires strong leadership commitment and clear communication throughout the organization.</p>
|
||||
''',
|
||||
'excerpt': 'A comprehensive guide to digital transformation strategies tailored for large enterprise organizations.',
|
||||
'author': authors['David Thompson'],
|
||||
'category': categories['digital-transformation'],
|
||||
'tags': ['Digital Transformation', 'Enterprise', 'Best Practices'],
|
||||
'thumbnail_url': '/images/blog/two.png',
|
||||
'featured': True,
|
||||
'reading_time': 10,
|
||||
'days_ago': 8
|
||||
},
|
||||
{
|
||||
'title': 'API-First Approach to System Integration',
|
||||
'content': '''
|
||||
<h2>What is API-First Development?</h2>
|
||||
<p>API-first development is a strategy where APIs are treated as first-class citizens in the development process. Instead of being an afterthought, APIs are designed and built before any other code.</p>
|
||||
|
||||
<h3>Benefits of API-First Approach</h3>
|
||||
<p>This approach offers numerous advantages including better developer experience, faster time to market, and improved system integration capabilities.</p>
|
||||
|
||||
<h3>RESTful API Design Principles</h3>
|
||||
<p>RESTful APIs follow specific architectural principles that make them scalable, maintainable, and easy to understand. Key principles include statelessness, resource-based URLs, and standard HTTP methods.</p>
|
||||
|
||||
<h2>Best Practices</h2>
|
||||
<ul>
|
||||
<li>Design APIs with clear and consistent naming conventions</li>
|
||||
<li>Version your APIs from the start</li>
|
||||
<li>Implement proper authentication and authorization</li>
|
||||
<li>Provide comprehensive documentation</li>
|
||||
<li>Use appropriate HTTP status codes</li>
|
||||
<li>Implement rate limiting and throttling</li>
|
||||
</ul>
|
||||
|
||||
<h3>GraphQL vs REST</h3>
|
||||
<p>While REST has been the standard for many years, GraphQL offers an alternative approach that can be more efficient for certain use cases. Understanding when to use each is crucial for effective API design.</p>
|
||||
''',
|
||||
'excerpt': 'Learn how an API-first approach can streamline system integration and improve your software architecture.',
|
||||
'author': authors['Emily Rodriguez'],
|
||||
'category': categories['system-integration'],
|
||||
'tags': ['API', 'REST', 'GraphQL', 'Integration'],
|
||||
'thumbnail_url': '/images/blog/three.png',
|
||||
'reading_time': 7,
|
||||
'days_ago': 12
|
||||
},
|
||||
{
|
||||
'title': 'Cloud Migration Best Practices for Enterprise',
|
||||
'content': '''
|
||||
<h2>Planning Your Cloud Migration</h2>
|
||||
<p>Cloud migration is a complex process that requires careful planning and execution. A well-thought-out strategy can mean the difference between success and costly setbacks.</p>
|
||||
|
||||
<h3>Assessing Your Current Infrastructure</h3>
|
||||
<p>Before migrating to the cloud, it's essential to thoroughly understand your current infrastructure, dependencies, and workload requirements.</p>
|
||||
|
||||
<h3>Choosing the Right Cloud Provider</h3>
|
||||
<p>AWS, Azure, and Google Cloud each offer unique strengths. The right choice depends on your specific requirements, existing technology stack, and business objectives.</p>
|
||||
|
||||
<h2>Migration Strategies</h2>
|
||||
<ol>
|
||||
<li><strong>Lift and Shift:</strong> Move applications as-is to the cloud</li>
|
||||
<li><strong>Replatform:</strong> Make minor optimizations during migration</li>
|
||||
<li><strong>Refactor:</strong> Redesign applications to be cloud-native</li>
|
||||
<li><strong>Rebuild:</strong> Completely rewrite applications for the cloud</li>
|
||||
</ol>
|
||||
|
||||
<h3>Security Considerations</h3>
|
||||
<p>Security should be a top priority during cloud migration. Implement encryption, access controls, and monitoring from day one.</p>
|
||||
|
||||
<h2>Post-Migration Optimization</h2>
|
||||
<p>Migration is just the beginning. Continuous optimization of costs, performance, and security is essential for maximizing cloud benefits.</p>
|
||||
''',
|
||||
'excerpt': 'Essential best practices and strategies for successfully migrating enterprise applications to the cloud.',
|
||||
'author': authors['Michael Chen'],
|
||||
'category': categories['cloud-solutions'],
|
||||
'tags': ['Cloud', 'AWS', 'Azure', 'Migration'],
|
||||
'thumbnail_url': '/images/blog/four.png',
|
||||
'featured': False,
|
||||
'reading_time': 9,
|
||||
'days_ago': 15
|
||||
},
|
||||
{
|
||||
'title': 'Enterprise Security in the Digital Age',
|
||||
'content': '''
|
||||
<h2>The Evolving Security Landscape</h2>
|
||||
<p>As enterprises embrace digital transformation, security challenges have become more complex. Organizations must adopt a comprehensive approach to protect their assets and data.</p>
|
||||
|
||||
<h3>Zero Trust Architecture</h3>
|
||||
<p>Zero Trust is a security model that assumes no user or system should be trusted by default, even if they're inside the network perimeter.</p>
|
||||
|
||||
<h3>Multi-Factor Authentication</h3>
|
||||
<p>MFA is no longer optional for enterprise security. Implementing strong authentication mechanisms is critical for protecting sensitive data and systems.</p>
|
||||
|
||||
<h2>Common Security Threats</h2>
|
||||
<ul>
|
||||
<li>Phishing and social engineering attacks</li>
|
||||
<li>Ransomware and malware</li>
|
||||
<li>Insider threats</li>
|
||||
<li>DDoS attacks</li>
|
||||
<li>API vulnerabilities</li>
|
||||
</ul>
|
||||
|
||||
<h3>Implementing a Security-First Culture</h3>
|
||||
<p>Technology alone cannot protect an organization. Security awareness training and a culture of security consciousness are equally important.</p>
|
||||
|
||||
<h2>Compliance and Regulations</h2>
|
||||
<p>Understanding and adhering to regulations like GDPR, HIPAA, and SOC 2 is essential for enterprise organizations operating in regulated industries.</p>
|
||||
''',
|
||||
'excerpt': 'Understanding modern security challenges and implementing robust security measures for enterprise environments.',
|
||||
'author': authors['Sarah Johnson'],
|
||||
'category': categories['security'],
|
||||
'tags': ['Security', 'Enterprise', 'Best Practices'],
|
||||
'thumbnail_url': '/images/blog/five.png',
|
||||
'reading_time': 6,
|
||||
'days_ago': 18
|
||||
},
|
||||
{
|
||||
'title': 'Building Scalable API Architectures',
|
||||
'content': '''
|
||||
<h2>Scalability Fundamentals</h2>
|
||||
<p>Building APIs that can scale to millions of requests requires careful architectural planning and implementation of proven patterns.</p>
|
||||
|
||||
<h3>Horizontal vs Vertical Scaling</h3>
|
||||
<p>Understanding the difference between horizontal and vertical scaling is crucial for building scalable systems. Most modern APIs benefit from horizontal scaling strategies.</p>
|
||||
|
||||
<h3>Caching Strategies</h3>
|
||||
<p>Implementing effective caching can dramatically improve API performance and reduce load on backend systems. Learn about different caching layers and when to use them.</p>
|
||||
|
||||
<h2>Performance Optimization</h2>
|
||||
<ul>
|
||||
<li>Database query optimization</li>
|
||||
<li>Connection pooling</li>
|
||||
<li>Asynchronous processing</li>
|
||||
<li>Load balancing</li>
|
||||
<li>CDN integration</li>
|
||||
</ul>
|
||||
|
||||
<h3>Monitoring and Observability</h3>
|
||||
<p>You can't optimize what you can't measure. Implementing comprehensive monitoring and logging is essential for maintaining scalable APIs.</p>
|
||||
|
||||
<h2>Rate Limiting and Throttling</h2>
|
||||
<p>Protecting your API from abuse and ensuring fair usage requires implementing rate limiting and throttling mechanisms.</p>
|
||||
''',
|
||||
'excerpt': 'Learn the principles and patterns for building API architectures that can scale to meet growing demands.',
|
||||
'author': authors['Emily Rodriguez'],
|
||||
'category': categories['api-development'],
|
||||
'tags': ['API', 'Architecture', 'Best Practices'],
|
||||
'thumbnail_url': '/images/blog/six.png',
|
||||
'reading_time': 8,
|
||||
'days_ago': 22
|
||||
},
|
||||
{
|
||||
'title': 'Microservices Architecture for Enterprise Applications',
|
||||
'content': '''
|
||||
<h2>Understanding Microservices</h2>
|
||||
<p>Microservices architecture has become the de facto standard for building scalable, maintainable enterprise applications. This architectural style structures an application as a collection of loosely coupled services.</p>
|
||||
|
||||
<h3>Key Characteristics</h3>
|
||||
<p>Microservices are independently deployable, organized around business capabilities, and can be written in different programming languages.</p>
|
||||
|
||||
<h3>Service Communication Patterns</h3>
|
||||
<p>Understanding different communication patterns between microservices is crucial. Options include synchronous REST APIs, message queues, and event-driven architectures.</p>
|
||||
|
||||
<h2>Design Patterns</h2>
|
||||
<ol>
|
||||
<li>API Gateway Pattern</li>
|
||||
<li>Service Discovery</li>
|
||||
<li>Circuit Breaker</li>
|
||||
<li>Event Sourcing</li>
|
||||
<li>CQRS (Command Query Responsibility Segregation)</li>
|
||||
</ol>
|
||||
|
||||
<h3>Containerization with Docker</h3>
|
||||
<p>Docker containers provide the perfect deployment vehicle for microservices, ensuring consistency across development and production environments.</p>
|
||||
|
||||
<h2>Challenges and Solutions</h2>
|
||||
<p>While microservices offer many benefits, they also introduce complexity in areas like distributed transactions, data consistency, and service orchestration.</p>
|
||||
''',
|
||||
'excerpt': 'A deep dive into microservices architecture patterns and best practices for enterprise applications.',
|
||||
'author': authors['Michael Chen'],
|
||||
'category': categories['enterprise-software'],
|
||||
'tags': ['Microservices', 'Architecture', 'Docker', 'Kubernetes'],
|
||||
'thumbnail_url': '/images/blog/seven.png',
|
||||
'featured': True,
|
||||
'reading_time': 11,
|
||||
'days_ago': 25
|
||||
},
|
||||
{
|
||||
'title': 'Data Analytics and Business Intelligence Solutions',
|
||||
'content': '''
|
||||
<h2>The Power of Data-Driven Decisions</h2>
|
||||
<p>In today's business environment, data analytics and business intelligence are no longer optional—they're essential for staying competitive.</p>
|
||||
|
||||
<h3>Modern BI Tools and Platforms</h3>
|
||||
<p>Modern BI platforms offer self-service analytics, real-time dashboards, and AI-powered insights that democratize data access across the organization.</p>
|
||||
|
||||
<h3>Building a Data Warehouse</h3>
|
||||
<p>A well-designed data warehouse serves as the foundation for business intelligence initiatives, providing a single source of truth for organizational data.</p>
|
||||
|
||||
<h2>Analytics Maturity Model</h2>
|
||||
<ol>
|
||||
<li>Descriptive Analytics - What happened?</li>
|
||||
<li>Diagnostic Analytics - Why did it happen?</li>
|
||||
<li>Predictive Analytics - What will happen?</li>
|
||||
<li>Prescriptive Analytics - What should we do?</li>
|
||||
</ol>
|
||||
|
||||
<h3>Real-Time Analytics</h3>
|
||||
<p>The ability to analyze data in real-time enables organizations to respond quickly to changing conditions and make timely decisions.</p>
|
||||
|
||||
<h2>Data Governance and Quality</h2>
|
||||
<p>Without proper data governance and quality management, analytics initiatives can produce unreliable results. Establishing data quality standards is crucial.</p>
|
||||
|
||||
<h3>Machine Learning Integration</h3>
|
||||
<p>Integrating machine learning models into BI platforms can provide deeper insights and enable predictive capabilities.</p>
|
||||
''',
|
||||
'excerpt': 'Leveraging data analytics and business intelligence to drive informed decision-making in enterprise environments.',
|
||||
'author': authors['David Thompson'],
|
||||
'category': categories['digital-transformation'],
|
||||
'tags': ['Analytics', 'BI', 'Enterprise', 'Best Practices'],
|
||||
'thumbnail_url': '/images/blog/eight.png',
|
||||
'reading_time': 9,
|
||||
'days_ago': 30
|
||||
}
|
||||
]
|
||||
|
||||
# Create posts
|
||||
for post_data in posts_data:
|
||||
tag_names = post_data.pop('tags')
|
||||
days_ago = post_data.pop('days_ago')
|
||||
|
||||
post = BlogPost.objects.create(
|
||||
**post_data,
|
||||
published_at=timezone.now() - timedelta(days=days_ago)
|
||||
)
|
||||
|
||||
# Add tags
|
||||
for tag_name in tag_names:
|
||||
if tag_name in tags:
|
||||
post.tags.add(tags[tag_name])
|
||||
|
||||
self.stdout.write(f'Created post: {post.title}')
|
||||
|
||||
self.stdout.write(self.style.SUCCESS('\nSuccessfully populated blog data!'))
|
||||
self.stdout.write(f'Created {BlogAuthor.objects.count()} authors')
|
||||
self.stdout.write(f'Created {BlogCategory.objects.count()} categories')
|
||||
self.stdout.write(f'Created {BlogTag.objects.count()} tags')
|
||||
self.stdout.write(f'Created {BlogPost.objects.count()} blog posts')
|
||||
|
||||
129
backEnd/blog/migrations/0001_initial.py
Normal file
129
backEnd/blog/migrations/0001_initial.py
Normal file
@@ -0,0 +1,129 @@
|
||||
# Generated by Django 4.2.7 on 2025-10-08 09:41
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='BlogAuthor',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200)),
|
||||
('email', models.EmailField(blank=True, max_length=254, null=True, unique=True)),
|
||||
('bio', models.TextField(blank=True)),
|
||||
('avatar', models.ImageField(blank=True, null=True, upload_to='blog/authors/')),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Blog Author',
|
||||
'verbose_name_plural': 'Blog Authors',
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='BlogCategory',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=100)),
|
||||
('slug', models.SlugField(max_length=100, unique=True)),
|
||||
('description', models.TextField(blank=True)),
|
||||
('display_order', models.PositiveIntegerField(default=0)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Blog Category',
|
||||
'verbose_name_plural': 'Blog Categories',
|
||||
'ordering': ['display_order', 'title'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='BlogTag',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=50, unique=True)),
|
||||
('slug', models.SlugField(unique=True)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Blog Tag',
|
||||
'verbose_name_plural': 'Blog Tags',
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='BlogPost',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=300)),
|
||||
('slug', models.SlugField(max_length=300, unique=True)),
|
||||
('content', models.TextField()),
|
||||
('excerpt', models.TextField(blank=True, help_text='Short excerpt for preview')),
|
||||
('thumbnail', models.ImageField(blank=True, null=True, upload_to='blog/thumbnails/')),
|
||||
('thumbnail_url', models.CharField(blank=True, help_text='External thumbnail URL', max_length=500)),
|
||||
('featured_image', models.ImageField(blank=True, null=True, upload_to='blog/featured/')),
|
||||
('featured_image_url', models.CharField(blank=True, help_text='External featured image URL', max_length=500)),
|
||||
('meta_description', models.CharField(blank=True, max_length=160)),
|
||||
('meta_keywords', models.CharField(blank=True, max_length=255)),
|
||||
('published', models.BooleanField(default=True)),
|
||||
('featured', models.BooleanField(default=False, help_text='Featured post')),
|
||||
('views_count', models.PositiveIntegerField(default=0)),
|
||||
('reading_time', models.PositiveIntegerField(default=5, help_text='Estimated reading time in minutes')),
|
||||
('published_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('author', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='posts', to='blog.blogauthor')),
|
||||
('category', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='posts', to='blog.blogcategory')),
|
||||
('tags', models.ManyToManyField(blank=True, related_name='posts', to='blog.blogtag')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Blog Post',
|
||||
'verbose_name_plural': 'Blog Posts',
|
||||
'ordering': ['-published_at', '-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='BlogComment',
|
||||
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)),
|
||||
('content', models.TextField()),
|
||||
('is_approved', models.BooleanField(default=False)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='replies', to='blog.blogcomment')),
|
||||
('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='blog.blogpost')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Blog Comment',
|
||||
'verbose_name_plural': 'Blog Comments',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='blogpost',
|
||||
index=models.Index(fields=['-published_at'], name='blog_blogpo_publish_e75c11_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='blogpost',
|
||||
index=models.Index(fields=['slug'], name='blog_blogpo_slug_361555_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='blogpost',
|
||||
index=models.Index(fields=['published'], name='blog_blogpo_publish_059755_idx'),
|
||||
),
|
||||
]
|
||||
0
backEnd/blog/migrations/__init__.py
Normal file
0
backEnd/blog/migrations/__init__.py
Normal file
BIN
backEnd/blog/migrations/__pycache__/0001_initial.cpython-312.pyc
Normal file
BIN
backEnd/blog/migrations/__pycache__/0001_initial.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backEnd/blog/migrations/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
backEnd/blog/migrations/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
178
backEnd/blog/models.py
Normal file
178
backEnd/blog/models.py
Normal file
@@ -0,0 +1,178 @@
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.utils.text import slugify
|
||||
|
||||
|
||||
class BlogAuthor(models.Model):
|
||||
"""Model for blog post authors"""
|
||||
name = models.CharField(max_length=200)
|
||||
email = models.EmailField(unique=True, blank=True, null=True)
|
||||
bio = models.TextField(blank=True)
|
||||
avatar = models.ImageField(upload_to='blog/authors/', blank=True, null=True)
|
||||
is_active = models.BooleanField(default=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Blog Author"
|
||||
verbose_name_plural = "Blog Authors"
|
||||
ordering = ['name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class BlogCategory(models.Model):
|
||||
"""Model for blog post categories"""
|
||||
title = models.CharField(max_length=100)
|
||||
slug = models.SlugField(max_length=100, unique=True)
|
||||
description = models.TextField(blank=True)
|
||||
display_order = models.PositiveIntegerField(default=0)
|
||||
is_active = models.BooleanField(default=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Blog Category"
|
||||
verbose_name_plural = "Blog Categories"
|
||||
ordering = ['display_order', 'title']
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.slug:
|
||||
self.slug = slugify(self.title)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class BlogTag(models.Model):
|
||||
"""Model for blog post tags"""
|
||||
name = models.CharField(max_length=50, unique=True)
|
||||
slug = models.SlugField(max_length=50, unique=True)
|
||||
is_active = models.BooleanField(default=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Blog Tag"
|
||||
verbose_name_plural = "Blog Tags"
|
||||
ordering = ['name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.slug:
|
||||
self.slug = slugify(self.name)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class BlogPost(models.Model):
|
||||
"""Model for blog posts"""
|
||||
title = models.CharField(max_length=300)
|
||||
slug = models.SlugField(max_length=300, unique=True)
|
||||
content = models.TextField()
|
||||
excerpt = models.TextField(blank=True, help_text="Short excerpt for preview")
|
||||
thumbnail = models.ImageField(upload_to='blog/thumbnails/', blank=True, null=True)
|
||||
thumbnail_url = models.CharField(max_length=500, blank=True, help_text="External thumbnail URL")
|
||||
featured_image = models.ImageField(upload_to='blog/featured/', blank=True, null=True)
|
||||
featured_image_url = models.CharField(max_length=500, blank=True, help_text="External featured image URL")
|
||||
|
||||
author = models.ForeignKey(
|
||||
BlogAuthor,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
related_name='posts'
|
||||
)
|
||||
category = models.ForeignKey(
|
||||
BlogCategory,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
related_name='posts'
|
||||
)
|
||||
tags = models.ManyToManyField(BlogTag, related_name='posts', blank=True)
|
||||
|
||||
# SEO and metadata
|
||||
meta_description = models.CharField(max_length=160, blank=True)
|
||||
meta_keywords = models.CharField(max_length=255, blank=True)
|
||||
|
||||
# Status and visibility
|
||||
published = models.BooleanField(default=True)
|
||||
featured = models.BooleanField(default=False, help_text="Featured post")
|
||||
views_count = models.PositiveIntegerField(default=0)
|
||||
reading_time = models.PositiveIntegerField(default=5, help_text="Estimated reading time in minutes")
|
||||
|
||||
# Timestamps
|
||||
published_at = models.DateTimeField(default=timezone.now)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Blog Post"
|
||||
verbose_name_plural = "Blog Posts"
|
||||
ordering = ['-published_at', '-created_at']
|
||||
indexes = [
|
||||
models.Index(fields=['-published_at']),
|
||||
models.Index(fields=['slug']),
|
||||
models.Index(fields=['published']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.slug:
|
||||
self.slug = slugify(self.title)
|
||||
if not self.excerpt and self.content:
|
||||
# Generate excerpt from content (first 200 characters)
|
||||
self.excerpt = self.content[:200] + '...' if len(self.content) > 200 else self.content
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def get_thumbnail_url(self):
|
||||
"""Return the thumbnail URL (uploaded image or external URL)"""
|
||||
if self.thumbnail and hasattr(self.thumbnail, 'url'):
|
||||
return self.thumbnail.url
|
||||
elif self.thumbnail_url:
|
||||
return self.thumbnail_url
|
||||
return None
|
||||
|
||||
@property
|
||||
def get_featured_image_url(self):
|
||||
"""Return the featured image URL (uploaded image or external URL)"""
|
||||
if self.featured_image and hasattr(self.featured_image, 'url'):
|
||||
return self.featured_image.url
|
||||
elif self.featured_image_url:
|
||||
return self.featured_image_url
|
||||
return None
|
||||
|
||||
def increment_views(self):
|
||||
"""Increment the view count"""
|
||||
self.views_count += 1
|
||||
self.save(update_fields=['views_count'])
|
||||
|
||||
|
||||
class BlogComment(models.Model):
|
||||
"""Model for blog post comments"""
|
||||
post = models.ForeignKey(BlogPost, on_delete=models.CASCADE, related_name='comments')
|
||||
name = models.CharField(max_length=100)
|
||||
email = models.EmailField()
|
||||
content = models.TextField()
|
||||
parent = models.ForeignKey(
|
||||
'self',
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='replies'
|
||||
)
|
||||
is_approved = models.BooleanField(default=False)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Blog Comment"
|
||||
verbose_name_plural = "Blog Comments"
|
||||
ordering = ['-created_at']
|
||||
|
||||
def __str__(self):
|
||||
return f"Comment by {self.name} on {self.post.title}"
|
||||
113
backEnd/blog/serializers.py
Normal file
113
backEnd/blog/serializers.py
Normal file
@@ -0,0 +1,113 @@
|
||||
from rest_framework import serializers
|
||||
from .models import BlogPost, BlogCategory, BlogAuthor, BlogTag, BlogComment
|
||||
|
||||
|
||||
class BlogAuthorSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for blog authors"""
|
||||
class Meta:
|
||||
model = BlogAuthor
|
||||
fields = ['id', 'name', 'email', 'bio', 'avatar']
|
||||
|
||||
|
||||
class BlogCategorySerializer(serializers.ModelSerializer):
|
||||
"""Serializer for blog categories"""
|
||||
posts_count = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = BlogCategory
|
||||
fields = ['id', 'title', 'slug', 'description', 'display_order', 'posts_count']
|
||||
|
||||
def get_posts_count(self, obj):
|
||||
return obj.posts.filter(published=True).count()
|
||||
|
||||
|
||||
class BlogTagSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for blog tags"""
|
||||
class Meta:
|
||||
model = BlogTag
|
||||
fields = ['id', 'name', 'slug']
|
||||
|
||||
|
||||
class BlogPostListSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for blog post list view"""
|
||||
author_name = serializers.CharField(source='author.name', read_only=True)
|
||||
category_title = serializers.CharField(source='category.title', read_only=True)
|
||||
category_slug = serializers.CharField(source='category.slug', read_only=True)
|
||||
thumbnail = serializers.SerializerMethodField()
|
||||
tags = BlogTagSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = BlogPost
|
||||
fields = [
|
||||
'id', 'title', 'slug', 'excerpt', 'thumbnail',
|
||||
'author_name', 'category_title', 'category_slug',
|
||||
'tags', 'published_at', 'created_at', 'updated_at',
|
||||
'views_count', 'reading_time', 'featured', 'published'
|
||||
]
|
||||
|
||||
def get_thumbnail(self, obj):
|
||||
return obj.get_thumbnail_url
|
||||
|
||||
|
||||
class BlogPostDetailSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for blog post detail view"""
|
||||
author = BlogAuthorSerializer(read_only=True)
|
||||
category = BlogCategorySerializer(read_only=True)
|
||||
tags = BlogTagSerializer(many=True, read_only=True)
|
||||
thumbnail = serializers.SerializerMethodField()
|
||||
featured_image = serializers.SerializerMethodField()
|
||||
related_posts = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = BlogPost
|
||||
fields = [
|
||||
'id', 'title', 'slug', 'content', 'excerpt',
|
||||
'thumbnail', 'featured_image', 'author', 'category', 'tags',
|
||||
'meta_description', 'meta_keywords',
|
||||
'published', 'featured', 'views_count', 'reading_time',
|
||||
'published_at', 'created_at', 'updated_at', 'related_posts'
|
||||
]
|
||||
|
||||
def get_thumbnail(self, obj):
|
||||
return obj.get_thumbnail_url
|
||||
|
||||
def get_featured_image(self, obj):
|
||||
return obj.get_featured_image_url
|
||||
|
||||
def get_related_posts(self, obj):
|
||||
"""Get related posts from the same category"""
|
||||
related = BlogPost.objects.filter(
|
||||
category=obj.category,
|
||||
published=True
|
||||
).exclude(id=obj.id)[:3]
|
||||
return BlogPostListSerializer(related, many=True, context=self.context).data
|
||||
|
||||
|
||||
class BlogCommentSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for blog comments"""
|
||||
replies = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = BlogComment
|
||||
fields = [
|
||||
'id', 'post', 'name', 'email', 'content',
|
||||
'parent', 'is_approved', 'created_at', 'updated_at', 'replies'
|
||||
]
|
||||
read_only_fields = ['is_approved', 'created_at', 'updated_at']
|
||||
|
||||
def get_replies(self, obj):
|
||||
"""Get nested replies"""
|
||||
if obj.replies.exists():
|
||||
return BlogCommentSerializer(
|
||||
obj.replies.filter(is_approved=True),
|
||||
many=True
|
||||
).data
|
||||
return []
|
||||
|
||||
|
||||
class BlogCommentCreateSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for creating blog comments"""
|
||||
class Meta:
|
||||
model = BlogComment
|
||||
fields = ['post', 'name', 'email', 'content', 'parent']
|
||||
|
||||
3
backEnd/blog/tests.py
Normal file
3
backEnd/blog/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
21
backEnd/blog/urls.py
Normal file
21
backEnd/blog/urls.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import (
|
||||
BlogPostViewSet,
|
||||
BlogCategoryViewSet,
|
||||
BlogAuthorViewSet,
|
||||
BlogTagViewSet,
|
||||
BlogCommentViewSet
|
||||
)
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'posts', BlogPostViewSet, basename='blog-post')
|
||||
router.register(r'categories', BlogCategoryViewSet, basename='blog-category')
|
||||
router.register(r'authors', BlogAuthorViewSet, basename='blog-author')
|
||||
router.register(r'tags', BlogTagViewSet, basename='blog-tag')
|
||||
router.register(r'comments', BlogCommentViewSet, basename='blog-comment')
|
||||
|
||||
urlpatterns = [
|
||||
path('', include(router.urls)),
|
||||
]
|
||||
|
||||
189
backEnd/blog/views.py
Normal file
189
backEnd/blog/views.py
Normal file
@@ -0,0 +1,189 @@
|
||||
from rest_framework import viewsets, status, filters
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.pagination import PageNumberPagination
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from django.db.models import Q
|
||||
from .models import BlogPost, BlogCategory, BlogAuthor, BlogTag, BlogComment
|
||||
from .serializers import (
|
||||
BlogPostListSerializer,
|
||||
BlogPostDetailSerializer,
|
||||
BlogCategorySerializer,
|
||||
BlogAuthorSerializer,
|
||||
BlogTagSerializer,
|
||||
BlogCommentSerializer,
|
||||
BlogCommentCreateSerializer
|
||||
)
|
||||
|
||||
|
||||
class BlogPagination(PageNumberPagination):
|
||||
"""Custom pagination for blog posts"""
|
||||
page_size = 9
|
||||
page_size_query_param = 'page_size'
|
||||
max_page_size = 100
|
||||
|
||||
|
||||
class BlogPostViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""
|
||||
ViewSet for blog posts
|
||||
Supports filtering by category, tags, author, and search
|
||||
"""
|
||||
queryset = BlogPost.objects.filter(published=True)
|
||||
pagination_class = BlogPagination
|
||||
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
|
||||
filterset_fields = ['category__slug', 'author', 'featured', 'tags__slug']
|
||||
search_fields = ['title', 'content', 'excerpt']
|
||||
ordering_fields = ['published_at', 'views_count', 'created_at']
|
||||
ordering = ['-published_at']
|
||||
lookup_field = 'slug'
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == 'retrieve':
|
||||
return BlogPostDetailSerializer
|
||||
return BlogPostListSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
|
||||
# Filter by category
|
||||
category_slug = self.request.query_params.get('category', None)
|
||||
if category_slug and category_slug != 'all':
|
||||
queryset = queryset.filter(category__slug=category_slug)
|
||||
|
||||
# Filter by tag
|
||||
tag_slug = self.request.query_params.get('tag', None)
|
||||
if tag_slug:
|
||||
queryset = queryset.filter(tags__slug=tag_slug)
|
||||
|
||||
# Filter by author
|
||||
author_id = self.request.query_params.get('author', None)
|
||||
if author_id:
|
||||
queryset = queryset.filter(author_id=author_id)
|
||||
|
||||
# Search query
|
||||
search = self.request.query_params.get('search', None)
|
||||
if search:
|
||||
queryset = queryset.filter(
|
||||
Q(title__icontains=search) |
|
||||
Q(content__icontains=search) |
|
||||
Q(excerpt__icontains=search)
|
||||
)
|
||||
|
||||
return queryset.distinct()
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
"""Override retrieve to increment view count"""
|
||||
instance = self.get_object()
|
||||
instance.increment_views()
|
||||
serializer = self.get_serializer(instance)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def featured(self, request):
|
||||
"""Get featured blog posts"""
|
||||
featured_posts = self.get_queryset().filter(featured=True)[:6]
|
||||
serializer = self.get_serializer(featured_posts, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def latest(self, request):
|
||||
"""Get latest blog posts"""
|
||||
limit = int(request.query_params.get('limit', 5))
|
||||
latest_posts = self.get_queryset()[:limit]
|
||||
serializer = self.get_serializer(latest_posts, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def popular(self, request):
|
||||
"""Get popular blog posts by views"""
|
||||
limit = int(request.query_params.get('limit', 5))
|
||||
popular_posts = self.get_queryset().order_by('-views_count')[:limit]
|
||||
serializer = self.get_serializer(popular_posts, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=True, methods=['get'])
|
||||
def related(self, request, slug=None):
|
||||
"""Get related posts for a specific post"""
|
||||
post = self.get_object()
|
||||
related_posts = self.get_queryset().filter(
|
||||
category=post.category
|
||||
).exclude(id=post.id)[:4]
|
||||
serializer = self.get_serializer(related_posts, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class BlogCategoryViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""ViewSet for blog categories"""
|
||||
queryset = BlogCategory.objects.filter(is_active=True)
|
||||
serializer_class = BlogCategorySerializer
|
||||
lookup_field = 'slug'
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def with_posts(self, request):
|
||||
"""Get categories that have published posts"""
|
||||
categories = self.get_queryset().filter(posts__published=True).distinct()
|
||||
serializer = self.get_serializer(categories, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class BlogAuthorViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""ViewSet for blog authors"""
|
||||
queryset = BlogAuthor.objects.filter(is_active=True)
|
||||
serializer_class = BlogAuthorSerializer
|
||||
|
||||
@action(detail=True, methods=['get'])
|
||||
def posts(self, request, pk=None):
|
||||
"""Get all posts by a specific author"""
|
||||
author = self.get_object()
|
||||
posts = BlogPost.objects.filter(author=author, published=True)
|
||||
page = self.paginate_queryset(posts)
|
||||
if page is not None:
|
||||
serializer = BlogPostListSerializer(page, many=True)
|
||||
return self.get_paginated_response(serializer.data)
|
||||
serializer = BlogPostListSerializer(posts, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class BlogTagViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""ViewSet for blog tags"""
|
||||
queryset = BlogTag.objects.filter(is_active=True)
|
||||
serializer_class = BlogTagSerializer
|
||||
lookup_field = 'slug'
|
||||
|
||||
@action(detail=True, methods=['get'])
|
||||
def posts(self, request, slug=None):
|
||||
"""Get all posts with a specific tag"""
|
||||
tag = self.get_object()
|
||||
posts = BlogPost.objects.filter(tags=tag, published=True)
|
||||
page = self.paginate_queryset(posts)
|
||||
if page is not None:
|
||||
serializer = BlogPostListSerializer(page, many=True)
|
||||
return self.get_paginated_response(serializer.data)
|
||||
serializer = BlogPostListSerializer(posts, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class BlogCommentViewSet(viewsets.ModelViewSet):
|
||||
"""ViewSet for blog comments"""
|
||||
queryset = BlogComment.objects.filter(is_approved=True)
|
||||
serializer_class = BlogCommentSerializer
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
filterset_fields = ['post']
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == 'create':
|
||||
return BlogCommentCreateSerializer
|
||||
return BlogCommentSerializer
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
"""Create a new comment (requires moderation)"""
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
self.perform_create(serializer)
|
||||
return Response(
|
||||
{
|
||||
'message': 'Comment submitted successfully. It will be visible after moderation.',
|
||||
'data': serializer.data
|
||||
},
|
||||
status=status.HTTP_201_CREATED
|
||||
)
|
||||
Reference in New Issue
Block a user