From 63dad15e474fd3afac02bbf70ed0825206c6a9e8 Mon Sep 17 00:00:00 2001 From: elsonkjimmy Date: Wed, 8 Oct 2025 14:41:07 +0100 Subject: [PATCH] improuvements's adding by @elsonkjimmy --- DEVELOPMENT.md | 137 +++++++++++++++++++ DjangoBox/settings.py | 4 + README.md | 45 +++++- core/admin.py | 24 +++- core/forms.py | 4 +- core/management/commands/import_feedbacks.py | 74 ++++++++++ core/models.py | 4 +- core/tests.py | 104 +++++++++++++- core/views.py | 20 ++- requirements.txt | 2 + start.bat | 47 +++++++ start.sh | 48 +++++++ static/css/custom.css | 65 +++++++++ templates/core/home.html | 28 +--- 14 files changed, 564 insertions(+), 42 deletions(-) create mode 100644 DEVELOPMENT.md create mode 100644 core/management/commands/import_feedbacks.py create mode 100644 requirements.txt create mode 100644 start.bat create mode 100755 start.sh create mode 100644 static/css/custom.css diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..f44a509 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,137 @@ +# Development Guide + +This document provides detailed information for developers working on DjangoBox. + +## Project Structure + +``` +DjangoBox/ +├── DjangoBox/ # Django project settings +│ ├── settings.py # Main settings file +│ ├── urls.py # Root URL configuration +│ └── wsgi.py # WSGI application +├── core/ # Main Django app +│ ├── models.py # Database models (Topic, Feedback) +│ ├── views.py # View functions +│ ├── forms.py # Django forms +│ ├── admin.py # Admin interface configuration +│ ├── urls.py # App URL patterns +│ ├── tests.py # Unit tests +│ └── management/ # Custom management commands +│ └── commands/ +│ └── import_feedbacks.py +├── templates/ # HTML templates +│ └── core/ +│ └── home.html # Main template +├── static/ # Static files (CSS, JS, images) +│ └── css/ +│ └── custom.css # Custom styles +├── data/ # Sample data +│ └── feedbacks.json # Sample feedback data +├── requirements.txt # Python dependencies +└── manage.py # Django management script +``` + +## Models + +### Topic +- `name`: CharField(100) - The topic name +- Used to categorize feedback entries + +### Feedback +- `user_name`: CharField(100) - Name of the person giving feedback +- `topic`: ForeignKey to Topic - The topic this feedback relates to +- `content`: MarkdownField - The feedback content (supports markdown) +- `content_rendered`: RenderedMarkdownField - Rendered HTML version +- `created_at`: DateTimeField - When the feedback was created + +## Views + +### feedback_view +- Handles both GET and POST requests +- GET: Displays the feedback form and paginated feedback list +- POST: Processes form submission (supports AJAX) +- Creates topics automatically if they don't exist + +## Forms + +### FeedbackForm +- ModelForm based on Feedback model +- Custom `topic_name` field for topic input +- Styled with Tailwind CSS classes +- Supports autocomplete for existing topics + +## Management Commands + +### import_feedbacks +- Imports feedback data from JSON files +- Usage: `python manage.py import_feedbacks [--file path/to/file.json]` +- Creates topics and feedbacks from JSON data +- Handles duplicate prevention + +## Testing + +Run tests with: +```bash +python manage.py test +``` + +Test coverage includes: +- Model creation and validation +- Form validation +- View functionality (GET, POST, AJAX) +- Pagination +- Admin interface + +## Static Files + +- Custom CSS in `static/css/custom.css` +- Tailwind CSS loaded from CDN +- Responsive design with modern UI +- Custom animations and hover effects + +## Admin Interface + +Access at `/admin/` with superuser credentials: +- Username: admin +- Password: admin123 + +Features: +- Topic management with feedback count +- Feedback management with search and filters +- Content preview for long feedbacks +- Date hierarchy for easy navigation + +## Development Workflow + +1. Make changes to models, views, or templates +2. Run migrations if models changed: `python manage.py makemigrations && python manage.py migrate` +3. Run tests: `python manage.py test` +4. Test manually: `python manage.py runserver` +5. Check admin interface for data management + +## Contributing + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Add tests for new functionality +5. Run the test suite +6. Submit a pull request + +## Troubleshooting + +### Common Issues + +1. **Migration errors**: Delete `db.sqlite3` and run `python manage.py migrate` +2. **Static files not loading**: Run `python manage.py collectstatic` +3. **Import errors**: Ensure virtual environment is activated +4. **Form validation errors**: Check that all required fields are provided + +### Debug Mode + +The project runs in DEBUG mode by default. For production: +1. Set `DEBUG = False` in settings.py +2. Configure `ALLOWED_HOSTS` +3. Set up proper static file serving +4. Use a production database (PostgreSQL recommended) \ No newline at end of file diff --git a/DjangoBox/settings.py b/DjangoBox/settings.py index ba9889f..ee5e369 100644 --- a/DjangoBox/settings.py +++ b/DjangoBox/settings.py @@ -119,6 +119,10 @@ # https://docs.djangoproject.com/en/4.2/howto/static-files/ STATIC_URL = 'static/' +STATIC_ROOT = BASE_DIR / 'staticfiles' +STATICFILES_DIRS = [ + BASE_DIR / 'static', +] # Default primary key field type # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field diff --git a/README.md b/README.md index 4252a9f..f76f273 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,45 @@ See `CONTRIBUTING.md` for details about how to contribute. ## Features -- Submit feedback entries (stored in a simple data layer) -- View feedback summaries +- Submit feedback entries with markdown support +- View feedback summaries with pagination +- AJAX form submission for better user experience +- Admin interface for managing feedbacks and topics +- Import sample data from JSON files +- Responsive design with modern UI +- Comprehensive test coverage - Small, focused Django app so contributors can get started quickly -## Quick start (development, Windows PowerShell) +## Quick start (development) -These steps assume you have Python 3.8+ installed. If the repository includes a `requirements.txt` file, prefer installing from it; otherwise install Django and other dependencies manually. +These steps assume you have Python 3.8+ installed. + +### Linux/macOS: + +1. Create and activate a virtual environment: + +```bash +python -m venv .venv +source .venv/bin/activate +``` + +2. Install dependencies: + +```bash +pip install -r requirements.txt +``` + +3. Prepare the database and run the development server: + +```bash +python manage.py migrate +python manage.py import_feedbacks # Import sample data +python manage.py runserver +``` + +4. Open http://127.0.0.1:8000/ in your browser to view the site. + +### Windows PowerShell: 1. Create and activate a virtual environment: @@ -26,18 +58,17 @@ python -m venv .venv .\.venv\Scripts\Activate.ps1 ``` -2. Install dependencies (if a `requirements.txt` exists): +2. Install dependencies: ```powershell pip install -r requirements.txt -# or, if no requirements file exists -pip install django ``` 3. Prepare the database and run the development server: ```powershell python manage.py migrate +python manage.py import_feedbacks # Import sample data python manage.py runserver ``` diff --git a/core/admin.py b/core/admin.py index 8c38f3f..08a5aac 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,3 +1,25 @@ from django.contrib import admin +from .models import Topic, Feedback -# Register your models here. + +@admin.register(Topic) +class TopicAdmin(admin.ModelAdmin): + list_display = ('name', 'feedback_count') + search_fields = ('name',) + + def feedback_count(self, obj): + return obj.feedback_set.count() + feedback_count.short_description = 'Number of feedbacks' + + +@admin.register(Feedback) +class FeedbackAdmin(admin.ModelAdmin): + list_display = ('user_name', 'topic', 'created_at', 'content_preview') + list_filter = ('topic', 'created_at') + search_fields = ('user_name', 'content', 'topic__name') + readonly_fields = ('created_at', 'content_rendered') + date_hierarchy = 'created_at' + + def content_preview(self, obj): + return obj.content[:100] + '...' if len(obj.content) > 100 else obj.content + content_preview.short_description = 'Content Preview' diff --git a/core/forms.py b/core/forms.py index d7d1252..e23bf8e 100644 --- a/core/forms.py +++ b/core/forms.py @@ -2,7 +2,7 @@ from .models import Feedback class FeedbackForm(forms.ModelForm): - topic = forms.CharField( + topic_name = forms.CharField( max_length=100, widget=forms.TextInput(attrs={ 'class': 'block w-full px-3 py-2 bg-slate-800 text-white border-blue-700 focus:border-indigo-400 focus:ring-indigo-400 rounded-xl shadow-sm focus:outline-none', @@ -14,7 +14,7 @@ class FeedbackForm(forms.ModelForm): class Meta: model = Feedback - fields = ['user_name', 'topic', 'content'] + fields = ['user_name', 'content'] widgets = { 'user_name': forms.TextInput(attrs={ 'class': 'block w-full px-3 py-2 bg-slate-800 text-white border-blue-700 focus:border-indigo-400 focus:ring-indigo-400 rounded-xl shadow-sm focus:outline-none', diff --git a/core/management/commands/import_feedbacks.py b/core/management/commands/import_feedbacks.py new file mode 100644 index 0000000..3826ee0 --- /dev/null +++ b/core/management/commands/import_feedbacks.py @@ -0,0 +1,74 @@ +from django.core.management.base import BaseCommand +from django.utils import timezone +from core.models import Topic, Feedback +import json +import os +from datetime import datetime + + +class Command(BaseCommand): + help = 'Import feedback data from JSON file' + + def add_arguments(self, parser): + parser.add_argument( + '--file', + type=str, + default='data/feedbacks.json', + help='Path to JSON file containing feedback data' + ) + + def handle(self, *args, **options): + file_path = options['file'] + + if not os.path.exists(file_path): + self.stdout.write( + self.style.ERROR(f'File {file_path} does not exist') + ) + return + + try: + with open(file_path, 'r', encoding='utf-8') as f: + data = json.load(f) + + imported_count = 0 + for item in data: + # Get or create topic + topic, created = Topic.objects.get_or_create( + name=item['topic'] + ) + + # Parse datetime if it exists + created_at = timezone.now() + if 'created_at' in item: + try: + created_at = datetime.fromisoformat( + item['created_at'].replace('Z', '+00:00') + ) + except ValueError: + pass + + # Create feedback + feedback, created = Feedback.objects.get_or_create( + user_name=item['user_name'], + topic=topic, + content=item['content'], + defaults={'created_at': created_at} + ) + + if created: + imported_count += 1 + + self.stdout.write( + self.style.SUCCESS( + f'Successfully imported {imported_count} feedback entries' + ) + ) + + except json.JSONDecodeError: + self.stdout.write( + self.style.ERROR('Invalid JSON file') + ) + except Exception as e: + self.stdout.write( + self.style.ERROR(f'Error importing data: {str(e)}') + ) \ No newline at end of file diff --git a/core/models.py b/core/models.py index ad416c1..41abb52 100644 --- a/core/models.py +++ b/core/models.py @@ -1,4 +1,5 @@ from django.db import models +import markdownfield.models # Create your models here. @@ -11,7 +12,8 @@ def __str__(self): class Feedback(models.Model): user_name = models.CharField(max_length=100) topic = models.ForeignKey(Topic, on_delete=models.CASCADE) - content = models.TextField() + content = markdownfield.models.MarkdownField(rendered_field='content_rendered') + content_rendered = markdownfield.models.RenderedMarkdownField() created_at = models.DateTimeField(auto_now_add=True) def __str__(self): diff --git a/core/tests.py b/core/tests.py index 7ce503c..26fba1a 100644 --- a/core/tests.py +++ b/core/tests.py @@ -1,3 +1,103 @@ -from django.test import TestCase +from django.test import TestCase, Client +from django.urls import reverse +from django.contrib.auth.models import User +from .models import Topic, Feedback +from .forms import FeedbackForm -# Create your tests here. + +class TopicModelTest(TestCase): + def test_topic_creation(self): + topic = Topic.objects.create(name="Test Topic") + self.assertEqual(str(topic), "Test Topic") + self.assertEqual(topic.name, "Test Topic") + + +class FeedbackModelTest(TestCase): + def setUp(self): + self.topic = Topic.objects.create(name="Test Topic") + + def test_feedback_creation(self): + feedback = Feedback.objects.create( + user_name="Test User", + topic=self.topic, + content="This is a test feedback" + ) + self.assertEqual(str(feedback), "Feedback by Test User on Test Topic") + self.assertEqual(feedback.user_name, "Test User") + self.assertEqual(feedback.topic, self.topic) + self.assertEqual(feedback.content, "This is a test feedback") + + +class FeedbackFormTest(TestCase): + def test_valid_form(self): + form_data = { + 'user_name': 'Test User', + 'topic_name': 'Test Topic', + 'content': 'This is test content' + } + form = FeedbackForm(data=form_data) + self.assertTrue(form.is_valid()) + + def test_invalid_form(self): + form_data = { + 'user_name': '', + 'topic_name': '', + 'content': '' + } + form = FeedbackForm(data=form_data) + self.assertFalse(form.is_valid()) + + +class FeedbackViewTest(TestCase): + def setUp(self): + self.client = Client() + self.topic = Topic.objects.create(name="Test Topic") + self.feedback = Feedback.objects.create( + user_name="Test User", + topic=self.topic, + content="Test feedback content" + ) + + def test_feedback_view_get(self): + response = self.client.get(reverse('core:home')) + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'DjangoBox Community Feedback') + self.assertContains(response, 'Test User') + + def test_feedback_view_post_valid(self): + form_data = { + 'user_name': 'New User', + 'topic_name': 'New Topic', + 'content': 'New feedback content' + } + response = self.client.post(reverse('core:home'), data=form_data) + self.assertEqual(response.status_code, 302) # Redirect after successful POST + self.assertTrue(Feedback.objects.filter(user_name='New User').exists()) + + def test_feedback_view_post_ajax(self): + form_data = { + 'user_name': 'AJAX User', + 'topic_name': 'AJAX Topic', + 'content': 'AJAX feedback content' + } + response = self.client.post( + reverse('core:home'), + data=form_data, + HTTP_X_REQUESTED_WITH='XMLHttpRequest' + ) + self.assertEqual(response.status_code, 200) + self.assertTrue(Feedback.objects.filter(user_name='AJAX User').exists()) + + def test_pagination(self): + # Create multiple feedbacks to test pagination + for i in range(15): + Feedback.objects.create( + user_name=f'User {i}', + topic=self.topic, + content=f'Feedback content {i}' + ) + + response = self.client.get(reverse('core:home')) + self.assertEqual(response.status_code, 200) + self.assertTrue('page_obj' in response.context) + self.assertEqual(len(response.context['page_obj']), 10) # First page should have 10 items diff --git a/core/views.py b/core/views.py index c9a9f07..a06f08c 100644 --- a/core/views.py +++ b/core/views.py @@ -2,6 +2,7 @@ from django.contrib import messages from django.urls import reverse from django.http import JsonResponse +from django.core.paginator import Paginator from .forms import FeedbackForm from .models import Topic, Feedback @@ -9,16 +10,20 @@ def feedback_view(request): topics = Topic.objects.values_list('name', flat=True) + + # Get all feedbacks for pagination + feedbacks = Feedback.objects.select_related('topic').order_by('-created_at') + paginator = Paginator(feedbacks, 10) # Show 10 feedbacks per page + page_number = request.GET.get('page') + page_obj = paginator.get_page(page_number) + if request.method == 'POST': form = FeedbackForm(request.POST) if form.is_valid(): - topic_name = form.cleaned_data.get('topic') + topic_name = form.cleaned_data.get('topic_name') topic_obj, _ = Topic.objects.get_or_create(name=topic_name) - feedback = Feedback( - user_name=form.cleaned_data['user_name'], - topic=topic_obj, - content=form.cleaned_data['content'] - ) + feedback = form.save(commit=False) + feedback.topic = topic_obj feedback.save() if request.headers.get('x-requested-with') == 'XMLHttpRequest': return JsonResponse({'success': True, 'message': 'Thank you for your feedback!'}) @@ -29,8 +34,9 @@ def feedback_view(request): return JsonResponse({'success': False, 'errors': form.errors.as_json()}) else: form = FeedbackForm() + return render( request, 'core/home.html', - {'form': form, 'topics': topics}, + {'form': form, 'topics': topics, 'page_obj': page_obj}, ) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e806194 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +Django==4.2.25 +django-markdownfield==0.11.0 \ No newline at end of file diff --git a/start.bat b/start.bat new file mode 100644 index 0000000..d3b0afc --- /dev/null +++ b/start.bat @@ -0,0 +1,47 @@ +@echo off +REM DjangoBox Quick Start Script for Windows +REM This script sets up and runs the DjangoBox project + +echo 🚀 Starting DjangoBox setup... + +REM Check if virtual environment exists +if not exist ".venv" ( + echo 📦 Creating virtual environment... + python -m venv .venv +) + +REM Activate virtual environment +echo 🔧 Activating virtual environment... +call .venv\Scripts\activate.bat + +REM Install dependencies +echo 📥 Installing dependencies... +pip install -r requirements.txt + +REM Run migrations +echo 🗄️ Running database migrations... +python manage.py migrate + +REM Import sample data +echo 📊 Importing sample data... +python manage.py import_feedbacks + +REM Create superuser if it doesn't exist +echo 👤 Creating admin user... +echo from django.contrib.auth.models import User; User.objects.create_superuser('admin', 'admin@example.com', 'admin123') if not User.objects.filter(username='admin').exists() else None | python manage.py shell + +REM Run tests +echo 🧪 Running tests... +python manage.py test + +echo ✅ Setup complete! +echo. +echo 🌐 Starting development server... +echo 📱 Open http://127.0.0.1:8000/ in your browser +echo 🔧 Admin interface: http://127.0.0.1:8000/admin/ (admin/admin123) +echo. +echo Press Ctrl+C to stop the server +echo. + +REM Start the development server +python manage.py runserver \ No newline at end of file diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..f5e50be --- /dev/null +++ b/start.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# DjangoBox Quick Start Script +# This script sets up and runs the DjangoBox project + +echo "🚀 Starting DjangoBox setup..." + +# Check if virtual environment exists +if [ ! -d ".venv" ]; then + echo "📦 Creating virtual environment..." + python -m venv .venv +fi + +# Activate virtual environment +echo "🔧 Activating virtual environment..." +source .venv/bin/activate + +# Install dependencies +echo "📥 Installing dependencies..." +pip install -r requirements.txt + +# Run migrations +echo "🗄️ Running database migrations..." +python manage.py migrate + +# Import sample data +echo "📊 Importing sample data..." +python manage.py import_feedbacks + +# Create superuser if it doesn't exist +echo "👤 Creating admin user..." +echo "from django.contrib.auth.models import User; User.objects.create_superuser('admin', 'admin@example.com', 'admin123') if not User.objects.filter(username='admin').exists() else None" | python manage.py shell + +# Run tests +echo "🧪 Running tests..." +python manage.py test + +echo "✅ Setup complete!" +echo "" +echo "🌐 Starting development server..." +echo "📱 Open http://127.0.0.1:8000/ in your browser" +echo "🔧 Admin interface: http://127.0.0.1:8000/admin/ (admin/admin123)" +echo "" +echo "Press Ctrl+C to stop the server" +echo "" + +# Start the development server +python manage.py runserver \ No newline at end of file diff --git a/static/css/custom.css b/static/css/custom.css new file mode 100644 index 0000000..cb750ab --- /dev/null +++ b/static/css/custom.css @@ -0,0 +1,65 @@ +/* Custom styles for DjangoBox */ + +/* Smooth scrolling */ +html { + scroll-behavior: smooth; +} + +/* Custom scrollbar */ +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + background: #1e293b; +} + +::-webkit-scrollbar-thumb { + background: #0ea5e9; + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: #0284c7; +} + +/* Animation for feedback cards */ +.feedback-card { + transition: all 0.3s ease; +} + +.feedback-card:hover { + transform: translateY(-2px); + box-shadow: 0 10px 25px rgba(16, 185, 129, 0.2); +} + +/* Loading animation */ +.loading { + display: inline-block; + width: 20px; + height: 20px; + border: 3px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + border-top-color: #fff; + animation: spin 1s ease-in-out infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +/* Success message animation */ +.success-message { + animation: slideInDown 0.5s ease-out; +} + +@keyframes slideInDown { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} \ No newline at end of file diff --git a/templates/core/home.html b/templates/core/home.html index b9beb60..cde7443 100644 --- a/templates/core/home.html +++ b/templates/core/home.html @@ -7,6 +7,8 @@ DjangoBox Feedbacks | Hacktoberfest 2025 + {% load static %} +