This commit is contained in:
Iliyan Angelov
2025-11-24 03:52:08 +02:00
parent dfcaebaf8c
commit 366f28677a
18241 changed files with 865352 additions and 567 deletions

View File

@@ -0,0 +1,208 @@
# Case Studies API
## Overview
The Case Studies API provides a comprehensive backend and frontend solution for managing and displaying case study content. This feature includes categories, clients, process steps, gallery images, and related case studies.
## Backend Structure
### Models
1. **CaseStudyCategory**
- Category management for case studies
- Fields: name, slug, description, display_order, is_active
2. **Client**
- Client information management
- Fields: name, slug, logo, description, website
3. **CaseStudy**
- Main case study model
- Fields: title, slug, subtitle, description, excerpt
- Images: thumbnail, featured_image, poster_image, project_image
- Relations: category, client
- Content: project_overview, site_map_content
4. **CaseStudyImage**
- Gallery images for case studies
- Fields: image, caption, display_order
5. **CaseStudyProcess**
- Process steps for case studies
- Fields: step_number, title, description
### API Endpoints
Base URL: `/api/case-studies/`
#### Case Studies
- `GET /case-studies/` - List all case studies (with pagination and filtering)
- Query params: `category`, `client`, `search`, `featured`, `ordering`, `page`, `page_size`
- `GET /case-studies/{slug}/` - Get case study details
- `GET /case-studies/featured/` - Get featured case studies
- `GET /case-studies/latest/?limit=6` - Get latest case studies
- `GET /case-studies/popular/?limit=6` - Get popular case studies
- `GET /case-studies/{slug}/related/` - Get related case studies
#### Categories
- `GET /categories/` - List all categories
- `GET /categories/{slug}/` - Get category details
- `GET /categories/with_case_studies/` - Get categories with case studies
#### Clients
- `GET /clients/` - List all clients
- `GET /clients/{slug}/` - Get client details
- `GET /clients/{slug}/case_studies/` - Get case studies for a client
## Frontend Structure
### API Service (`lib/api/caseStudyService.ts`)
Provides TypeScript functions for all API endpoints with proper typing.
### Hooks (`lib/hooks/useCaseStudy.ts`)
React hooks for data fetching:
- `useCaseStudies()` - Fetch all case studies
- `useCaseStudy(slug)` - Fetch single case study
- `useFeaturedCaseStudies()` - Fetch featured case studies
- `useLatestCaseStudies()` - Fetch latest case studies
- `usePopularCaseStudies()` - Fetch popular case studies
- `useRelatedCaseStudies(slug)` - Fetch related case studies
- `useCaseStudyCategories()` - Fetch categories
- `useClients()` - Fetch clients
### Components
1. **CaseItems** (`components/pages/case-study/CaseItems.tsx`)
- Lists all case studies in a grid
- Includes tab navigation for "Case Study" and "Client" views
- Dynamically rendered from API data
2. **CaseSingle** (`components/pages/case-study/CaseSingle.tsx`)
- Displays detailed case study information
- Shows poster image, project overview, and gallery
- Dynamically rendered from API data
3. **Process** (`components/pages/case-study/Process.tsx`)
- Displays process steps for a case study
- Dynamically rendered from API data
4. **RelatedCase** (`components/pages/case-study/RelatedCase.tsx`)
- Shows related case studies
- Dynamically rendered from API data
### Pages
- `/case-study` - Lists all case studies
- `/case-study/[slug]` - Displays individual case study
## Setup Instructions
### Backend Setup
1. Add `'case_studies'` to `INSTALLED_APPS` in `settings.py`
2. Run migrations:
```bash
python manage.py makemigrations case_studies
python manage.py migrate case_studies
```
3. Populate sample data:
```bash
python manage.py populate_case_studies
```
4. Add URL patterns to main `urls.py`:
```python
path('api/case-studies/', include('case_studies.urls')),
```
### Frontend Setup
The frontend is already integrated and ready to use. The components will automatically fetch data from the API when the pages load.
## Admin Panel
Access the Django admin panel to manage:
- Case Study Categories
- Clients
- Case Studies
- Case Study Images
- Case Study Process Steps
URL: `/admin/`
## Data Population
The `populate_case_studies` management command creates:
- 6 case study categories
- 4 clients
- 8 case studies
- 32 gallery images
- 40 process steps
## Features
✅ Full CRUD operations via Django admin
✅ RESTful API with filtering and pagination
✅ Dynamic frontend with React hooks
✅ Image support (local and external URLs)
✅ Related case studies
✅ Process steps with ordering
✅ Gallery images with captions
✅ Category and client management
✅ Featured and popular case studies
✅ SEO fields (meta description, keywords)
✅ View count tracking
## Testing the API
You can test the API using:
1. Django REST Framework browsable API:
- Navigate to `http://localhost:8000/api/case-studies/case-studies/`
2. Swagger UI:
- Navigate to `http://localhost:8000/swagger/`
3. Frontend:
- Navigate to `http://localhost:3000/case-study`
## Example API Response
```json
{
"id": 1,
"title": "Artificial intelligence is the simulation of human processes",
"slug": "artificial-intelligence-is-the-simulation-of-human-processes",
"subtitle": "AI-Powered Business Solutions",
"excerpt": "This artificial intelligence project demonstrates...",
"thumbnail": "/images/case/one.png",
"category": {
"id": 4,
"name": "AI",
"slug": "ai"
},
"client": {
"id": 1,
"name": "Tarapio",
"slug": "tarapio"
},
"gallery_images": [...],
"process_steps": [...],
"related_case_studies": [...]
}
```
## Notes
- All images support both uploaded files and external URLs
- Slugs are automatically generated from titles
- Case studies are ordered by display_order and published_at
- Only published case studies are returned via the API
- View counts are automatically incremented when viewing details

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,105 @@
from django.contrib import admin
from .models import CaseStudy, CaseStudyCategory, Client, CaseStudyImage, CaseStudyProcess
@admin.register(CaseStudyCategory)
class CaseStudyCategoryAdmin(admin.ModelAdmin):
list_display = ['name', 'slug', 'display_order', 'is_active', 'case_studies_count']
list_filter = ['is_active']
search_fields = ['name', 'slug']
prepopulated_fields = {'slug': ('name',)}
ordering = ['display_order', 'name']
def case_studies_count(self, obj):
return obj.case_studies.count()
case_studies_count.short_description = 'Case Studies Count'
@admin.register(Client)
class ClientAdmin(admin.ModelAdmin):
list_display = ['name', 'slug', 'website', 'is_active', 'case_studies_count']
list_filter = ['is_active', 'created_at']
search_fields = ['name', 'slug', 'website']
prepopulated_fields = {'slug': ('name',)}
ordering = ['name']
def case_studies_count(self, obj):
return obj.case_studies.count()
case_studies_count.short_description = 'Case Studies Count'
class CaseStudyImageInline(admin.TabularInline):
model = CaseStudyImage
extra = 1
fields = ['image', 'image_url', 'caption', 'display_order']
class CaseStudyProcessInline(admin.TabularInline):
model = CaseStudyProcess
extra = 1
fields = ['step_number', 'title', 'description']
ordering = ['step_number']
@admin.register(CaseStudy)
class CaseStudyAdmin(admin.ModelAdmin):
list_display = [
'title', 'category', 'client', 'published',
'featured', 'views_count', 'display_order', 'published_at'
]
list_filter = [
'published', 'featured', 'category',
'client', 'published_at', 'created_at'
]
search_fields = ['title', 'description', 'excerpt']
prepopulated_fields = {'slug': ('title',)}
date_hierarchy = 'published_at'
ordering = ['display_order', '-published_at', '-created_at']
inlines = [CaseStudyImageInline, CaseStudyProcessInline]
fieldsets = (
('Basic Information', {
'fields': ('title', 'slug', 'subtitle', 'category', 'client')
}),
('Content', {
'fields': ('excerpt', 'description', 'project_overview', 'site_map_content')
}),
('Images', {
'fields': (
'thumbnail', 'thumbnail_url',
'featured_image', 'featured_image_url',
'poster_image', 'poster_image_url',
'project_image', 'project_image_url'
)
}),
('SEO', {
'fields': ('meta_description', 'meta_keywords'),
'classes': ('collapse',)
}),
('Status & Visibility', {
'fields': ('published', 'featured', 'display_order', 'published_at')
}),
('Statistics', {
'fields': ('views_count',),
'classes': ('collapse',)
}),
)
readonly_fields = ['views_count']
@admin.register(CaseStudyImage)
class CaseStudyImageAdmin(admin.ModelAdmin):
list_display = ['case_study', 'caption', 'display_order', 'created_at']
list_filter = ['case_study', 'created_at']
search_fields = ['case_study__title', 'caption']
ordering = ['case_study', 'display_order']
@admin.register(CaseStudyProcess)
class CaseStudyProcessAdmin(admin.ModelAdmin):
list_display = ['case_study', 'step_number', 'title', 'created_at']
list_filter = ['case_study', 'created_at']
search_fields = ['case_study__title', 'title', 'description']
ordering = ['case_study', 'step_number']

View File

@@ -0,0 +1,7 @@
from django.apps import AppConfig
class CaseStudiesConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'case_studies'

View File

@@ -0,0 +1,346 @@
from django.core.management.base import BaseCommand
from case_studies.models import CaseStudyCategory, Client, CaseStudy, CaseStudyImage, CaseStudyProcess
from django.utils import timezone
from datetime import timedelta
class Command(BaseCommand):
help = 'Populate database with sample case study data'
def handle(self, *args, **options):
self.stdout.write(self.style.SUCCESS('Starting to populate case study data...'))
# Clear existing data
CaseStudyProcess.objects.all().delete()
CaseStudyImage.objects.all().delete()
CaseStudy.objects.all().delete()
Client.objects.all().delete()
CaseStudyCategory.objects.all().delete()
# Create Categories
categories_data = [
{
'name': '3D Render',
'slug': '3d-render',
'description': '3D rendering and visualization projects',
'display_order': 1
},
{
'name': 'UI / UX',
'slug': 'ui-ux',
'description': 'User interface and user experience design projects',
'display_order': 2
},
{
'name': 'Photography',
'slug': 'photography',
'description': 'Professional photography projects',
'display_order': 3
},
{
'name': 'AI',
'slug': 'ai',
'description': 'Artificial intelligence and machine learning projects',
'display_order': 4
},
{
'name': 'Icon Set',
'slug': 'icon-set',
'description': 'Custom icon set design projects',
'display_order': 5
},
{
'name': 'Road Map',
'slug': 'road-map',
'description': 'Product roadmap and planning projects',
'display_order': 6
}
]
categories = {}
for cat_data in categories_data:
category = CaseStudyCategory.objects.create(**cat_data)
categories[category.slug] = category
self.stdout.write(f'Created category: {category.name}')
# Create Clients
clients_data = [
{
'name': 'Tarapio',
'slug': 'tarapio',
'description': 'Leading technology solutions provider',
'website': 'https://tarapio.com'
},
{
'name': 'Melenpo',
'slug': 'melenpo',
'description': 'Digital innovation company',
'website': 'https://melenpo.com'
},
{
'name': 'Polax',
'slug': 'polax',
'description': 'Enterprise software solutions',
'website': 'https://polax.com'
},
{
'name': 'AINA',
'slug': 'aina',
'description': 'AI and automation solutions',
'website': 'https://aina.com'
}
]
clients = {}
for client_data in clients_data:
client = Client.objects.create(**client_data)
clients[client.slug] = client
self.stdout.write(f'Created client: {client.name}')
# Create Case Studies
case_studies_data = [
{
'title': '3D computer graphics, or "3D graphics',
'subtitle': 'Immersive 3D Visualization Experience',
'description': '''
<h2>Project Overview</h2>
<p>A comprehensive 3D rendering project that showcases cutting-edge visualization techniques and photorealistic rendering capabilities. This project demonstrates our expertise in creating stunning visual content for modern digital platforms.</p>
<h3>The Challenge</h3>
<p>Our client needed high-quality 3D visualizations that could accurately represent their products in a digital environment. The challenge was to create renders that were not only photorealistic but also optimized for various platforms and use cases.</p>
<h3>Our Approach</h3>
<p>We employed advanced 3D modeling techniques combined with physically-based rendering (PBR) to achieve exceptional results. Our team utilized industry-standard tools and custom workflows to deliver renders that exceeded client expectations.</p>
<h3>Key Features</h3>
<ul>
<li>Photorealistic lighting and materials</li>
<li>High-resolution textures and details</li>
<li>Multiple viewing angles and perspectives</li>
<li>Optimized assets for web and print</li>
<li>Interactive 3D viewer integration</li>
</ul>
<h2>Results</h2>
<p>The project resulted in a collection of stunning 3D renders that significantly enhanced the client's digital presence. The visualizations led to increased customer engagement and improved conversion rates across their digital channels.</p>
''',
'category': categories['3d-render'],
'client': None,
'thumbnail_url': '/images/case/two.png',
'poster_image_url': '/images/case/poster.png',
'project_image_url': '/images/case/project.png',
'project_overview': 'Lorem ipsum dolor sit amet consectetur. Vestibulum malesuada amet sagittis urna. Mattis eget ultricies est morbi velit ultrices viverra elit facilisi.',
'site_map_content': 'Lorem ipsum dolor sit amet consectetur. Vestibulum malesuada amet sagittis urna. Mattis eget ultricies est morbi velit ultrices viverra elit facilisi.',
'featured': True,
'display_order': 1,
'days_ago': 10
},
{
'title': 'Artificial intelligence is the simulation of human processes',
'subtitle': 'AI-Powered Business Solutions',
'description': '''
<h2>About the Project</h2>
<p>This artificial intelligence project demonstrates the power of machine learning and AI in solving complex business problems. We developed custom AI models that automate decision-making processes and provide predictive insights.</p>
<h3>Technology Stack</h3>
<p>The project utilizes state-of-the-art AI technologies including neural networks, natural language processing, and computer vision. Our solution is built on a scalable cloud infrastructure that can handle large volumes of data processing.</p>
<h3>Implementation</h3>
<p>We worked closely with the client to understand their specific needs and challenges. The implementation phase involved data collection, model training, validation, and deployment to production environments.</p>
<h2>Impact</h2>
<p>The AI solution has transformed the client's operations, reducing manual work by 60% and improving accuracy in decision-making processes. The system continues to learn and improve over time, providing increasing value to the organization.</p>
''',
'category': categories['ai'],
'client': clients['tarapio'],
'thumbnail_url': '/images/case/one.png',
'poster_image_url': '/images/case/poster.png',
'featured': True,
'display_order': 2,
'days_ago': 15
},
{
'title': 'User experience (UX) design is the process design teams',
'subtitle': 'Modern UX Design System',
'description': '''
<h2>Project Summary</h2>
<p>A comprehensive UX design project focused on creating intuitive and engaging user experiences. This case study showcases our approach to user-centered design and our ability to create interfaces that delight users.</p>
<h3>Research and Discovery</h3>
<p>We conducted extensive user research including interviews, surveys, and usability testing to understand user needs and pain points. This research formed the foundation of our design decisions.</p>
<h3>Design Process</h3>
<p>Our design process involved creating user personas, journey maps, wireframes, and high-fidelity prototypes. Each step was validated with real users to ensure we were on the right track.</p>
<h2>Deliverables</h2>
<ul>
<li>Comprehensive design system</li>
<li>Interactive prototypes</li>
<li>User flow diagrams</li>
<li>Accessibility guidelines</li>
<li>Component library</li>
</ul>
''',
'category': categories['ui-ux'],
'client': clients['melenpo'],
'thumbnail_url': '/images/case/three.png',
'poster_image_url': '/images/case/poster.png',
'featured': False,
'display_order': 3,
'days_ago': 20
},
{
'title': 'Photography is the art, application, and practice',
'subtitle': 'Professional Photography Portfolio',
'description': '''
<h2>About This Project</h2>
<p>A stunning photography project that captures the essence of modern visual storytelling. This portfolio demonstrates our expertise in various photography styles and techniques.</p>
<h3>Photography Style</h3>
<p>The project incorporates multiple photography styles including product photography, portrait photography, and architectural photography. Each image is carefully composed and post-processed to achieve the desired aesthetic.</p>
<h2>Technical Excellence</h2>
<p>Using professional-grade equipment and advanced lighting techniques, we created images that stand out in quality and artistic vision.</p>
''',
'category': categories['photography'],
'client': None,
'thumbnail_url': '/images/case/four.png',
'poster_image_url': '/images/case/poster.png',
'featured': False,
'display_order': 4,
'days_ago': 25
},
{
'title': 'UX case study for a medical app- medical product design',
'subtitle': 'Healthcare UX Innovation',
'description': '''
<h2>Healthcare Design Challenge</h2>
<p>Designing for healthcare requires special attention to accessibility, security, and user trust. This medical app design project showcases our expertise in creating intuitive interfaces for complex healthcare workflows.</p>
<h3>Compliance and Security</h3>
<p>The design adheres to HIPAA regulations and industry best practices for healthcare data security while maintaining an intuitive user experience.</p>
<h2>User Research</h2>
<p>We conducted extensive research with healthcare professionals and patients to ensure the design meets the needs of all stakeholders.</p>
''',
'category': categories['ui-ux'],
'client': clients['polax'],
'thumbnail_url': '/images/case/five.png',
'poster_image_url': '/images/case/poster.png',
'featured': False,
'display_order': 5,
'days_ago': 30
},
{
'title': 'Make icon set for the educational project',
'subtitle': 'Custom Educational Icon Set',
'description': '''
<h2>Icon Design Project</h2>
<p>A comprehensive icon set designed specifically for educational platforms. This project demonstrates our ability to create cohesive, scalable, and meaningful iconography.</p>
<h3>Design Principles</h3>
<p>The icons follow consistent design principles including size, style, and metaphor. Each icon is designed to be instantly recognizable and appropriate for educational contexts.</p>
<h2>Deliverables</h2>
<p>The final deliverable includes icons in multiple formats (SVG, PNG) and sizes, along with comprehensive usage guidelines.</p>
''',
'category': categories['icon-set'],
'client': None,
'thumbnail_url': '/images/case/six.png',
'poster_image_url': '/images/case/poster.png',
'featured': False,
'display_order': 6,
'days_ago': 35
},
{
'title': 'AI-driven user experience design process',
'subtitle': 'AI-Driven User Experience',
'description': '''
<h2>AI-Enhanced UX Design</h2>
<p>This project combines artificial intelligence with user experience design to create adaptive interfaces that learn from user behavior and preferences.</p>
<h3>Innovation</h3>
<p>The design incorporates machine learning algorithms that personalize the user experience based on individual usage patterns and preferences.</p>
''',
'category': categories['ai'],
'client': clients['aina'],
'thumbnail_url': '/images/case/seven.png',
'poster_image_url': '/images/case/poster.png',
'featured': False,
'display_order': 7,
'days_ago': 40
},
{
'title': 'UX site rode map app product design system',
'subtitle': 'Product Roadmap Visualization',
'description': '''
<h2>Product Roadmap Design</h2>
<p>A comprehensive product roadmap visualization system that helps teams plan, communicate, and execute their product strategy effectively.</p>
<h3>Features</h3>
<p>The roadmap system includes timeline views, milestone tracking, dependency mapping, and collaboration tools for distributed teams.</p>
<h2>Implementation</h2>
<p>Built with modern web technologies and optimized for performance and usability across all devices and platforms.</p>
''',
'category': categories['road-map'],
'client': None,
'thumbnail_url': '/images/case/eight.png',
'poster_image_url': '/images/case/poster.png',
'featured': False,
'display_order': 8,
'days_ago': 45
}
]
# Create case studies
created_case_studies = []
for cs_data in case_studies_data:
days_ago = cs_data.pop('days_ago')
case_study = CaseStudy.objects.create(
**cs_data,
published_at=timezone.now() - timedelta(days=days_ago)
)
created_case_studies.append(case_study)
# Add gallery images
gallery_images = [
'/images/case/nine.png',
'/images/case/ten.png',
'/images/case/eleven.png',
'/images/case/twelve.png'
]
for idx, img_url in enumerate(gallery_images, 1):
CaseStudyImage.objects.create(
case_study=case_study,
image_url=img_url,
caption=f'Gallery Image {idx}',
display_order=idx
)
# Add process steps
processes = [
{'step_number': 1, 'title': 'Computer Vision', 'description': 'Quisque varius malesuada dui, ut posuere purus gravida in. Phasellus ultricies ullamcorper mollis.'},
{'step_number': 2, 'title': 'Computer Vision', 'description': 'Quisque varius malesuada dui, ut posuere purus gravida in. Phasellus ultricies ullamcorper mollis.'},
{'step_number': 3, 'title': '3D Vision', 'description': 'Quisque varius malesuada dui, ut posuere purus gravida in. Phasellus ultricies ullamcorper mollis.'},
{'step_number': 4, 'title': 'Computer Vision', 'description': 'Quisque varius malesuada dui, ut posuere purus gravida in. Phasellus ultricies ullamcorper mollis.'},
{'step_number': 5, 'title': '3D Vision', 'description': 'Quisque varius malesuada dui, ut posuere purus gravida in. Phasellus ultricies ullamcorper mollis.'},
]
for process_data in processes:
CaseStudyProcess.objects.create(
case_study=case_study,
**process_data
)
self.stdout.write(f'Created case study: {case_study.title}')
self.stdout.write(self.style.SUCCESS('\nSuccessfully populated case study data!'))
self.stdout.write(f'Created {CaseStudyCategory.objects.count()} categories')
self.stdout.write(f'Created {Client.objects.count()} clients')
self.stdout.write(f'Created {CaseStudy.objects.count()} case studies')
self.stdout.write(f'Created {CaseStudyImage.objects.count()} gallery images')
self.stdout.write(f'Created {CaseStudyProcess.objects.count()} process steps')

View File

@@ -0,0 +1,144 @@
# Generated by Django 4.2.7 on 2025-10-08 10:24
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='CaseStudy',
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)),
('subtitle', models.CharField(blank=True, max_length=300)),
('description', models.TextField()),
('excerpt', models.TextField(blank=True, help_text='Short excerpt for preview')),
('thumbnail', models.ImageField(blank=True, null=True, upload_to='case_studies/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='case_studies/featured/')),
('featured_image_url', models.CharField(blank=True, help_text='External featured image URL', max_length=500)),
('poster_image', models.ImageField(blank=True, null=True, upload_to='case_studies/posters/')),
('poster_image_url', models.CharField(blank=True, help_text='External poster image URL', max_length=500)),
('project_image', models.ImageField(blank=True, null=True, upload_to='case_studies/projects/')),
('project_image_url', models.CharField(blank=True, help_text='External project image URL', max_length=500)),
('project_overview', models.TextField(blank=True, help_text='Project overview content')),
('site_map_content', models.TextField(blank=True, help_text='Site map / process content')),
('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 case study')),
('views_count', models.PositiveIntegerField(default=0)),
('display_order', models.PositiveIntegerField(default=0)),
('published_at', models.DateTimeField(default=django.utils.timezone.now)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'verbose_name': 'Case Study',
'verbose_name_plural': 'Case Studies',
'ordering': ['display_order', '-published_at', '-created_at'],
},
),
migrations.CreateModel(
name='CaseStudyCategory',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', 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': 'Case Study Category',
'verbose_name_plural': 'Case Study Categories',
'ordering': ['display_order', 'name'],
},
),
migrations.CreateModel(
name='Client',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200)),
('slug', models.SlugField(max_length=200, unique=True)),
('logo', models.ImageField(blank=True, null=True, upload_to='case_studies/clients/')),
('logo_url', models.CharField(blank=True, help_text='External logo URL', max_length=500)),
('description', models.TextField(blank=True)),
('website', models.URLField(blank=True)),
('is_active', models.BooleanField(default=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'verbose_name': 'Client',
'verbose_name_plural': 'Clients',
'ordering': ['name'],
},
),
migrations.CreateModel(
name='CaseStudyProcess',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=200)),
('description', models.TextField()),
('step_number', models.PositiveIntegerField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('case_study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='process_steps', to='case_studies.casestudy')),
],
options={
'verbose_name': 'Case Study Process',
'verbose_name_plural': 'Case Study Processes',
'ordering': ['step_number'],
},
),
migrations.CreateModel(
name='CaseStudyImage',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('image', models.ImageField(upload_to='case_studies/gallery/')),
('image_url', models.CharField(blank=True, help_text='External image URL', max_length=500)),
('caption', models.CharField(blank=True, max_length=200)),
('display_order', models.PositiveIntegerField(default=0)),
('created_at', models.DateTimeField(auto_now_add=True)),
('case_study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='gallery_images', to='case_studies.casestudy')),
],
options={
'verbose_name': 'Case Study Image',
'verbose_name_plural': 'Case Study Images',
'ordering': ['display_order', 'created_at'],
},
),
migrations.AddField(
model_name='casestudy',
name='category',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='case_studies', to='case_studies.casestudycategory'),
),
migrations.AddField(
model_name='casestudy',
name='client',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='case_studies', to='case_studies.client'),
),
migrations.AddIndex(
model_name='casestudy',
index=models.Index(fields=['-published_at'], name='case_studie_publish_77559c_idx'),
),
migrations.AddIndex(
model_name='casestudy',
index=models.Index(fields=['slug'], name='case_studie_slug_2898e2_idx'),
),
migrations.AddIndex(
model_name='casestudy',
index=models.Index(fields=['published'], name='case_studie_publish_c9c1aa_idx'),
),
]

View File

@@ -0,0 +1,222 @@
from django.db import models
from django.utils import timezone
from django.utils.text import slugify
class CaseStudyCategory(models.Model):
"""Model for case study categories"""
name = 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 = "Case Study Category"
verbose_name_plural = "Case Study Categories"
ordering = ['display_order', '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 Client(models.Model):
"""Model for clients"""
name = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique=True)
logo = models.ImageField(upload_to='case_studies/clients/', blank=True, null=True)
logo_url = models.CharField(max_length=500, blank=True, help_text="External logo URL")
description = models.TextField(blank=True)
website = models.URLField(blank=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 = "Client"
verbose_name_plural = "Clients"
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)
@property
def get_logo_url(self):
"""Return the logo URL (uploaded image or external URL)"""
if self.logo and hasattr(self.logo, 'url'):
return self.logo.url
elif self.logo_url:
return self.logo_url
return None
class CaseStudy(models.Model):
"""Model for case studies"""
title = models.CharField(max_length=300)
slug = models.SlugField(max_length=300, unique=True)
subtitle = models.CharField(max_length=300, blank=True)
description = models.TextField()
excerpt = models.TextField(blank=True, help_text="Short excerpt for preview")
# Images
thumbnail = models.ImageField(upload_to='case_studies/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='case_studies/featured/', blank=True, null=True)
featured_image_url = models.CharField(max_length=500, blank=True, help_text="External featured image URL")
poster_image = models.ImageField(upload_to='case_studies/posters/', blank=True, null=True)
poster_image_url = models.CharField(max_length=500, blank=True, help_text="External poster image URL")
project_image = models.ImageField(upload_to='case_studies/projects/', blank=True, null=True)
project_image_url = models.CharField(max_length=500, blank=True, help_text="External project image URL")
# Content sections
project_overview = models.TextField(blank=True, help_text="Project overview content")
site_map_content = models.TextField(blank=True, help_text="Site map / process content")
# Relations
category = models.ForeignKey(
CaseStudyCategory,
on_delete=models.SET_NULL,
null=True,
related_name='case_studies'
)
client = models.ForeignKey(
Client,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='case_studies'
)
# 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 case study")
views_count = models.PositiveIntegerField(default=0)
display_order = models.PositiveIntegerField(default=0)
# 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 = "Case Study"
verbose_name_plural = "Case Studies"
ordering = ['display_order', '-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.description:
# Generate excerpt from description (first 200 characters)
self.excerpt = self.description[:200] + '...' if len(self.description) > 200 else self.description
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
@property
def get_poster_image_url(self):
"""Return the poster image URL (uploaded image or external URL)"""
if self.poster_image and hasattr(self.poster_image, 'url'):
return self.poster_image.url
elif self.poster_image_url:
return self.poster_image_url
return None
@property
def get_project_image_url(self):
"""Return the project image URL (uploaded image or external URL)"""
if self.project_image and hasattr(self.project_image, 'url'):
return self.project_image.url
elif self.project_image_url:
return self.project_image_url
return None
def increment_views(self):
"""Increment the view count"""
self.views_count += 1
self.save(update_fields=['views_count'])
class CaseStudyImage(models.Model):
"""Model for additional case study images (gallery)"""
case_study = models.ForeignKey(CaseStudy, on_delete=models.CASCADE, related_name='gallery_images')
image = models.ImageField(upload_to='case_studies/gallery/')
image_url = models.CharField(max_length=500, blank=True, help_text="External image URL")
caption = models.CharField(max_length=200, blank=True)
display_order = models.PositiveIntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = "Case Study Image"
verbose_name_plural = "Case Study Images"
ordering = ['display_order', 'created_at']
def __str__(self):
return f"{self.case_study.title} - Image {self.display_order}"
@property
def get_image_url(self):
"""Return the image URL (uploaded image or external URL)"""
if self.image and hasattr(self.image, 'url'):
return self.image.url
elif self.image_url:
return self.image_url
return None
class CaseStudyProcess(models.Model):
"""Model for case study process steps"""
case_study = models.ForeignKey(CaseStudy, on_delete=models.CASCADE, related_name='process_steps')
title = models.CharField(max_length=200)
description = models.TextField()
step_number = models.PositiveIntegerField()
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = "Case Study Process"
verbose_name_plural = "Case Study Processes"
ordering = ['step_number']
def __str__(self):
return f"{self.case_study.title} - Step {self.step_number}: {self.title}"

View File

@@ -0,0 +1,111 @@
from rest_framework import serializers
from .models import CaseStudy, CaseStudyCategory, Client, CaseStudyImage, CaseStudyProcess
class CaseStudyCategorySerializer(serializers.ModelSerializer):
"""Serializer for case study categories"""
case_studies_count = serializers.SerializerMethodField()
class Meta:
model = CaseStudyCategory
fields = ['id', 'name', 'slug', 'description', 'display_order', 'case_studies_count']
def get_case_studies_count(self, obj):
return obj.case_studies.filter(published=True).count()
class ClientSerializer(serializers.ModelSerializer):
"""Serializer for clients"""
logo = serializers.SerializerMethodField()
class Meta:
model = Client
fields = ['id', 'name', 'slug', 'logo', 'description', 'website']
def get_logo(self, obj):
return obj.get_logo_url
class CaseStudyImageSerializer(serializers.ModelSerializer):
"""Serializer for case study gallery images"""
image = serializers.SerializerMethodField()
class Meta:
model = CaseStudyImage
fields = ['id', 'image', 'caption', 'display_order']
def get_image(self, obj):
return obj.get_image_url
class CaseStudyProcessSerializer(serializers.ModelSerializer):
"""Serializer for case study process steps"""
class Meta:
model = CaseStudyProcess
fields = ['id', 'title', 'description', 'step_number']
class CaseStudyListSerializer(serializers.ModelSerializer):
"""Serializer for case study list view"""
category_name = serializers.CharField(source='category.name', read_only=True)
category_slug = serializers.CharField(source='category.slug', read_only=True)
client_name = serializers.CharField(source='client.name', read_only=True, allow_null=True)
thumbnail = serializers.SerializerMethodField()
class Meta:
model = CaseStudy
fields = [
'id', 'title', 'slug', 'subtitle', 'excerpt', 'thumbnail',
'category_name', 'category_slug', 'client_name',
'published_at', 'created_at', 'updated_at',
'views_count', 'featured', 'published', 'display_order'
]
def get_thumbnail(self, obj):
return obj.get_thumbnail_url
class CaseStudyDetailSerializer(serializers.ModelSerializer):
"""Serializer for case study detail view"""
category = CaseStudyCategorySerializer(read_only=True)
client = ClientSerializer(read_only=True)
thumbnail = serializers.SerializerMethodField()
featured_image = serializers.SerializerMethodField()
poster_image = serializers.SerializerMethodField()
project_image = serializers.SerializerMethodField()
gallery_images = CaseStudyImageSerializer(many=True, read_only=True)
process_steps = CaseStudyProcessSerializer(many=True, read_only=True)
related_case_studies = serializers.SerializerMethodField()
class Meta:
model = CaseStudy
fields = [
'id', 'title', 'slug', 'subtitle', 'description', 'excerpt',
'thumbnail', 'featured_image', 'poster_image', 'project_image',
'project_overview', 'site_map_content',
'category', 'client', 'gallery_images', 'process_steps',
'meta_description', 'meta_keywords',
'published', 'featured', 'views_count', 'display_order',
'published_at', 'created_at', 'updated_at', 'related_case_studies'
]
def get_thumbnail(self, obj):
return obj.get_thumbnail_url
def get_featured_image(self, obj):
return obj.get_featured_image_url
def get_poster_image(self, obj):
return obj.get_poster_image_url
def get_project_image(self, obj):
return obj.get_project_image_url
def get_related_case_studies(self, obj):
"""Get related case studies from the same category"""
related = CaseStudy.objects.filter(
category=obj.category,
published=True
).exclude(id=obj.id)[:3]
return CaseStudyListSerializer(related, many=True, context=self.context).data

View File

@@ -0,0 +1,5 @@
# Test case for case_studies app
from django.test import TestCase
# Create your tests here.

View File

@@ -0,0 +1,17 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import (
CaseStudyViewSet,
CaseStudyCategoryViewSet,
ClientViewSet
)
router = DefaultRouter()
router.register(r'case-studies', CaseStudyViewSet, basename='case-study')
router.register(r'categories', CaseStudyCategoryViewSet, basename='case-study-category')
router.register(r'clients', ClientViewSet, basename='client')
urlpatterns = [
path('', include(router.urls)),
]

View File

@@ -0,0 +1,138 @@
from rest_framework import viewsets, 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 CaseStudy, CaseStudyCategory, Client
from .serializers import (
CaseStudyListSerializer,
CaseStudyDetailSerializer,
CaseStudyCategorySerializer,
ClientSerializer
)
class CaseStudyPagination(PageNumberPagination):
"""Custom pagination for case studies"""
page_size = 9
page_size_query_param = 'page_size'
max_page_size = 100
class CaseStudyViewSet(viewsets.ReadOnlyModelViewSet):
"""
ViewSet for case studies
Supports filtering by category, client, and search
"""
queryset = CaseStudy.objects.filter(published=True)
pagination_class = CaseStudyPagination
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ['category__slug', 'client', 'featured']
search_fields = ['title', 'description', 'excerpt']
ordering_fields = ['published_at', 'views_count', 'created_at', 'display_order']
ordering = ['display_order', '-published_at']
lookup_field = 'slug'
def get_serializer_class(self):
if self.action == 'retrieve':
return CaseStudyDetailSerializer
return CaseStudyListSerializer
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 client
client_slug = self.request.query_params.get('client', None)
if client_slug:
queryset = queryset.filter(client__slug=client_slug)
# Search query
search = self.request.query_params.get('search', None)
if search:
queryset = queryset.filter(
Q(title__icontains=search) |
Q(description__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 case studies"""
featured_case_studies = self.get_queryset().filter(featured=True)[:6]
serializer = self.get_serializer(featured_case_studies, many=True)
return Response(serializer.data)
@action(detail=False, methods=['get'])
def latest(self, request):
"""Get latest case studies"""
limit = int(request.query_params.get('limit', 6))
latest_case_studies = self.get_queryset()[:limit]
serializer = self.get_serializer(latest_case_studies, many=True)
return Response(serializer.data)
@action(detail=False, methods=['get'])
def popular(self, request):
"""Get popular case studies by views"""
limit = int(request.query_params.get('limit', 6))
popular_case_studies = self.get_queryset().order_by('-views_count')[:limit]
serializer = self.get_serializer(popular_case_studies, many=True)
return Response(serializer.data)
@action(detail=True, methods=['get'])
def related(self, request, slug=None):
"""Get related case studies for a specific case study"""
case_study = self.get_object()
related_case_studies = self.get_queryset().filter(
category=case_study.category
).exclude(id=case_study.id)[:4]
serializer = self.get_serializer(related_case_studies, many=True)
return Response(serializer.data)
class CaseStudyCategoryViewSet(viewsets.ReadOnlyModelViewSet):
"""ViewSet for case study categories"""
queryset = CaseStudyCategory.objects.filter(is_active=True)
serializer_class = CaseStudyCategorySerializer
lookup_field = 'slug'
@action(detail=False, methods=['get'])
def with_case_studies(self, request):
"""Get categories that have published case studies"""
categories = self.get_queryset().filter(case_studies__published=True).distinct()
serializer = self.get_serializer(categories, many=True)
return Response(serializer.data)
class ClientViewSet(viewsets.ReadOnlyModelViewSet):
"""ViewSet for clients"""
queryset = Client.objects.filter(is_active=True)
serializer_class = ClientSerializer
lookup_field = 'slug'
@action(detail=True, methods=['get'])
def case_studies(self, request, slug=None):
"""Get all case studies for a specific client"""
client = self.get_object()
case_studies = CaseStudy.objects.filter(client=client, published=True)
page = self.paginate_queryset(case_studies)
if page is not None:
serializer = CaseStudyListSerializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = CaseStudyListSerializer(case_studies, many=True)
return Response(serializer.data)