Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions README.En.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
🌍 Available languages: **English** | [Français](README.md)

# Website API

A RESTful API built with Django and Django REST Framework to manage website content, including blog features, events, and user management.

## 📋 Prerequisites

- Python 3.8+
- PostgreSQL 12+
- Redis (for caching and queues)
- pip (Python package manager)

## 🚀 Installation

1. **Clone the repository**
```bash
git clone https://github.com/charles-kamga/website_api.git
cd website_api
```

2. **Set up the virtual environment**
```bash
python -m venv venv
source venv/bin/activate # On Windows: .\venv\Scripts\activate
```

3. **Install dependencies**
```bash
pip install -r requirements.txt
```

4. **Configure the PostgreSQL database**
```sql
-- Connect to PostgreSQL
sudo -u postgres psql

-- Create the database
CREATE DATABASE django_website_db;

-- Create a user (replace values in brackets)
CREATE USER [db_user] WITH PASSWORD '[your_password]';

-- Grant privileges
GRANT ALL PRIVILEGES ON DATABASE django_website_db TO [db_user];
```

5. **Configure environment variables**
```bash
cp .env.example .env
```

Edit the `.env` file with your settings:
- `DB_*`: Database connection parameters
- `SECRET_KEY`: Django secret key (generate a new one for production)
- `EMAIL_*`: SMTP configuration for emails
- `TWILLIO_*`: Twilio credentials for SMS verification (optional)

6. **Apply migrations**
```bash
python manage.py migrate
```

7. **Create a superuser (optional)**
```bash
python manage.py createsuperuser
```

8. **Run the development server**
```bash
python manage.py runserver
```

The API will be available at: http://127.0.0.1:8000/
The admin interface will be available at: http://127.0.0.1:8000/admin/

## 🏗 Project Structure

```
website_api/
├── apps/ # Django applications
│ ├── blog/ # Blog article management
│ ├── events/ # Events management
│ └── users/ # User management and authentication
├── config/ # Project configuration
├── documentation/ # Additional documentation
├── middlewares/ # Custom middlewares
├── services/ # Business logic
└── website_api/ # Main project settings
```

## 🔧 Environment Variables

| Variable | Description | Default Value |
|----------|-------------|----------------|
| `DEBUG` | Debug mode | `True` in development, `False` in production |
| `SECRET_KEY` | Django secret key | Must be defined in production |
| `DB_*` | Database settings | See `.env.example` |
| `EMAIL_*` | SMTP configuration | Must be set for emails |
| `REDIS_URL` | Redis connection URL | `redis://127.0.0.1:6379` |
| `TWILLIO_*` | Twilio credentials (SMS) | Optional |

## 📚 API Documentation

The API documentation is available at `/api/docs/` when the server is running.

## 🧪 Running Tests

```bash
# Run all tests
python manage.py test

# Run tests for a specific app
python manage.py test apps.users
```

## 🛠 Development Tools

- **Linting**: `flake8`
- **Formatting**: `black`
- **Import sorting**: `isort`

## 🤝 Contributing

Contributions are welcome! Here’s how to contribute:

1. Fork the project
2. Create a feature branch (`git checkout -b feature/my-new-feature`)
3. Commit your changes (`git commit -am 'Add new feature'`)
4. Push the branch (`git push origin feature/my-new-feature`)
5. Create a Pull Request

## 📄 License

This project is licensed under the MIT License – see the [LICENSE](LICENSE) file for details.

## 📧 Contact

For any questions, please open an issue on GitHub or contact the development team.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
🌍 Langues disponibles : [English](README.En.md) | **Français**

# Website API

Une API RESTful construite avec Django et Django REST Framework pour gérer le contenu d'un site web, incluant des fonctionnalités de blog, d'événements et de gestion d'utilisateurs.
Expand Down
6 changes: 3 additions & 3 deletions apps/events/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .event import Event, EventCity, EventRegion, EventVenue, EventTag
from .reservation import Reservation
from .speaker import Speaker, SpeakerSocialMedia, SpeakerSpeciality, AvailableSocialMedia
from .event import Event, EventCity, EventRegion, EventVenue, EventTag
from .reservation import Reservation
from .speaker import Speaker, SpeakerSocialMedia, SpeakerSpeciality, AvailableSocialMedia
50 changes: 25 additions & 25 deletions apps/events/models/partners.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from apps.users.models import BaseModel
class Partner(BaseModel):
name = models.CharField(
max_length=255, unique=True,
help_text=_("The name of the partner"), verbose_name=_("Name")
)
logo = models.URLField(
help_text=_("The logo of the partner"), verbose_name=_("Logo")
)
about = models.TextField(
help_text=_("About the partner"), verbose_name=_("About")
)
website = models.URLField(
help_text=_("The website of the partner"), verbose_name=_("Website")
)
def __str__(self):
return self.name
class Meta:
verbose_name = _("Partner")
from django.db import models
from django.utils.translation import gettext_lazy as _
from apps.users.models import BaseModel


class Partner(BaseModel):
name = models.CharField(
max_length=255, unique=True,
help_text=_("The name of the partner"), verbose_name=_("Name")
)
logo = models.URLField(
help_text=_("The logo of the partner"), verbose_name=_("Logo")
)
about = models.TextField(
help_text=_("About the partner"), verbose_name=_("About")
)
website = models.URLField(
help_text=_("The website of the partner"), verbose_name=_("Website")
)

def __str__(self):
return self.name

class Meta:
verbose_name = _("Partner")
verbose_name_plural = _("Partners")
56 changes: 28 additions & 28 deletions apps/events/models/projects.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from apps.users.models import BaseModel
class Event(BaseModel):
"""
Event model
"""
title = models.CharField(max_length=50, verbose_name=_("Title"))
description = models.TextField(verbose_name=_("Description"))
date = models.DateField(verbose_name=_("Date"))
maintainers = models.ManyToManyField(
"users.User", verbose_name=_("Maintainers"),
help_text=_("Maintainers of the event"), related_name="maintained_events",
)
github_link = models.URLField(
null=True, blank=True, verbose_name=_("GitHub Link"),
help_text=_("Link to the GitHub repository"),
)
class Meta:
db_table = "events"
verbose_name = _("Event")
verbose_name_plural = _("Events")
# Gallery to be added here later from: https://developers.google.com/photos/library/guides/overview
from django.db import models
from django.utils.translation import gettext_lazy as _

from apps.users.models import BaseModel


class Event(BaseModel):
"""
Event model
"""
title = models.CharField(max_length=50, verbose_name=_("Title"))
description = models.TextField(verbose_name=_("Description"))
date = models.DateField(verbose_name=_("Date"))
maintainers = models.ManyToManyField(
"users.User", verbose_name=_("Maintainers"),
help_text=_("Maintainers of the event"), related_name="maintained_events",
)
github_link = models.URLField(
null=True, blank=True, verbose_name=_("GitHub Link"),
help_text=_("Link to the GitHub repository"),
)

class Meta:
db_table = "events"
verbose_name = _("Event")
verbose_name_plural = _("Events")

# Gallery to be added here later from: https://developers.google.com/photos/library/guides/overview
14 changes: 7 additions & 7 deletions apps/events/routes/extra.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.urls import path
from apps.events.views.uploader import FileUploadView
urlpatterns = [
path('upload/', FileUploadView.as_view(), name='file-upload'),
]
from django.urls import path

from apps.events.views.uploader import FileUploadView

urlpatterns = [
path('upload/', FileUploadView.as_view(), name='file-upload'),
]
10 changes: 5 additions & 5 deletions apps/events/serializers/upload_serializer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from rest_framework import serializers
class UploadSerializer(serializers.Serializer):
file = serializers.URLField()
from rest_framework import serializers


class UploadSerializer(serializers.Serializer):
file = serializers.URLField()
18 changes: 9 additions & 9 deletions apps/events/signals/main.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from django.db.models.signals import post_save
from django.dispatch import receiver
from apps.events.models import Speaker, SpeakerSocialMedia
@receiver(post_save, sender=Speaker)
def update_speaker_social_media_active_status(sender, instance, **kwargs):
social_media_accounts = SpeakerSocialMedia.objects.filter(speaker=instance)
for account in social_media_accounts:
account.active = instance.active
from django.db.models.signals import post_save
from django.dispatch import receiver
from apps.events.models import Speaker, SpeakerSocialMedia

@receiver(post_save, sender=Speaker)
def update_speaker_social_media_active_status(sender, instance, **kwargs):
social_media_accounts = SpeakerSocialMedia.objects.filter(speaker=instance)
for account in social_media_accounts:
account.active = instance.active
account.save()
82 changes: 41 additions & 41 deletions apps/events/views/uploader.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,41 @@
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
from django.http import JsonResponse
from drf_spectacular.utils import extend_schema, OpenApiResponse
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
from apps.events.serializers.upload_serializer import UploadSerializer
from mixins import APIResponseMixin
from rest_framework.parsers import MultiPartParser
class FileUploadView(APIView, APIResponseMixin):
"""
View to handle file uploads.
"""
permission_classes = [IsAuthenticated]
serializer_class = UploadSerializer
parser_classes = [MultiPartParser]
@extend_schema(
operation_id="Upload a file",
description="Upload a file to the server.",
tags=["File Upload"],
request=UploadSerializer,
responses={201: OpenApiResponse(description="File uploaded successfully")},
)
def post(self, request, *args, **kwargs):
if 'file' not in request.FILES:
return JsonResponse({'error': 'No file provided'}, status=400)
file = request.FILES['file']
file_name = default_storage.save(file.name, ContentFile(file.read()))
file_url = default_storage.url(file_name)
return self.success(
message='File uploaded successfully',
data={'file_url': file_url},
status_code=status.HTTP_201_CREATED
)
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
from django.http import JsonResponse
from drf_spectacular.utils import extend_schema, OpenApiResponse
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView

from apps.events.serializers.upload_serializer import UploadSerializer
from mixins import APIResponseMixin
from rest_framework.parsers import MultiPartParser

class FileUploadView(APIView, APIResponseMixin):
"""
View to handle file uploads.
"""
permission_classes = [IsAuthenticated]
serializer_class = UploadSerializer
parser_classes = [MultiPartParser]


@extend_schema(
operation_id="Upload a file",
description="Upload a file to the server.",
tags=["File Upload"],
request=UploadSerializer,
responses={201: OpenApiResponse(description="File uploaded successfully")},
)
def post(self, request, *args, **kwargs):
if 'file' not in request.FILES:
return JsonResponse({'error': 'No file provided'}, status=400)

file = request.FILES['file']
file_name = default_storage.save(file.name, ContentFile(file.read()))
file_url = default_storage.url(file_name)

return self.success(
message='File uploaded successfully',
data={'file_url': file_url},
status_code=status.HTTP_201_CREATED
)
Loading