update
This commit is contained in:
208
backEnd/case_studies/README.md
Normal file
208
backEnd/case_studies/README.md
Normal 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
|
||||
|
||||
0
backEnd/case_studies/__init__.py
Normal file
0
backEnd/case_studies/__init__.py
Normal file
BIN
backEnd/case_studies/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
backEnd/case_studies/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backEnd/case_studies/__pycache__/admin.cpython-312.pyc
Normal file
BIN
backEnd/case_studies/__pycache__/admin.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backEnd/case_studies/__pycache__/apps.cpython-312.pyc
Normal file
BIN
backEnd/case_studies/__pycache__/apps.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backEnd/case_studies/__pycache__/models.cpython-312.pyc
Normal file
BIN
backEnd/case_studies/__pycache__/models.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backEnd/case_studies/__pycache__/serializers.cpython-312.pyc
Normal file
BIN
backEnd/case_studies/__pycache__/serializers.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backEnd/case_studies/__pycache__/urls.cpython-312.pyc
Normal file
BIN
backEnd/case_studies/__pycache__/urls.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backEnd/case_studies/__pycache__/views.cpython-312.pyc
Normal file
BIN
backEnd/case_studies/__pycache__/views.cpython-312.pyc
Normal file
Binary file not shown.
105
backEnd/case_studies/admin.py
Normal file
105
backEnd/case_studies/admin.py
Normal 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']
|
||||
|
||||
7
backEnd/case_studies/apps.py
Normal file
7
backEnd/case_studies/apps.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CaseStudiesConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'case_studies'
|
||||
|
||||
0
backEnd/case_studies/management/__init__.py
Normal file
0
backEnd/case_studies/management/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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')
|
||||
|
||||
144
backEnd/case_studies/migrations/0001_initial.py
Normal file
144
backEnd/case_studies/migrations/0001_initial.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
0
backEnd/case_studies/migrations/__init__.py
Normal file
0
backEnd/case_studies/migrations/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
222
backEnd/case_studies/models.py
Normal file
222
backEnd/case_studies/models.py
Normal 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}"
|
||||
|
||||
111
backEnd/case_studies/serializers.py
Normal file
111
backEnd/case_studies/serializers.py
Normal 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
|
||||
|
||||
5
backEnd/case_studies/tests.py
Normal file
5
backEnd/case_studies/tests.py
Normal file
@@ -0,0 +1,5 @@
|
||||
# Test case for case_studies app
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
17
backEnd/case_studies/urls.py
Normal file
17
backEnd/case_studies/urls.py
Normal 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)),
|
||||
]
|
||||
|
||||
138
backEnd/case_studies/views.py
Normal file
138
backEnd/case_studies/views.py
Normal 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)
|
||||
|
||||
Reference in New Issue
Block a user