API for managing travel projects and places to visit. The system integrates with Art Institute of Chicago API for validation and retrieving information about places.
- CRUD operations for travel projects
- Managing places in projects (up to 10 places per project)
- Integration with Art Institute of Chicago API
- Place validation through external API
- Automatic tracking of project completion status
- JWT authentication for all endpoints
- Caching of external API requests
- OpenAPI/Swagger documentation
- Pagination and filtering
- Python 3.11+
- PostgreSQL
- Docker and Docker Compose (optional)
- Clone the repository:
git clone <repository-url>
cd TravelProject- Create a virtual environment:
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate- Install dependencies:
pip install -r requirements.txt- Create a
.envfile in the project root:
SECRET_KEY=your-secret-key-here
DEBUG=True
ALLOWED_HOSTS=localhost,127.0.0.1
DB_NAME=travel_db
DB_USER=postgres
DB_PASSWORD=postgres
DB_HOST=localhost
DB_PORT=5432- Run migrations:
cd config
python manage.py migrate- Start the development server:
python manage.py runserver-
Make sure the
.envfile is created (see above) -
Start the containers:
cd docker
docker-compose up --buildAPI will be available at http://localhost:8000
All API endpoints require JWT authentication.
POST /api/register/
Content-Type: application/json
{
"username": "newuser",
"email": "user@example.com",
"password": "securepassword123",
"password_confirm": "securepassword123"
}Response:
{
"message": "User created successfully",
"username": "newuser",
"email": "user@example.com"
}POST /api/token/
Content-Type: application/json
{
"username": "your_username",
"password": "your_password"
}Response:
{
"access": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGc..."
}POST /api/token/refresh/
Content-Type: application/json
{
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGc..."
}Include the token in the Authorization header:
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc...
You can also create users via Django admin:
cd config
python manage.py createsuperuserThen access admin panel at /admin/
Note: All endpoints below require JWT authentication.
GET /api/projects/- List projects (with pagination and filtering)POST /api/projects/- Create project (can include array of places)GET /api/projects/{id}/- Get projectPUT /api/projects/{id}/- Update projectPATCH /api/projects/{id}/- Partial update of projectDELETE /api/projects/{id}/- Delete project (not allowed if any places are visited)
GET /api/projects/{project_id}/places/- List project placesPOST /api/projects/{project_id}/places/- Add place to projectGET /api/projects/{project_id}/places/{id}/- Get placePUT /api/projects/{project_id}/places/{id}/- Update placePATCH /api/projects/{project_id}/places/{id}/- Partial update of placeDELETE /api/projects/{project_id}/places/{id}/- Delete placeGET /api/places/search-artworks/?query=monet&limit=10- Search artworks in Art Institute API to find external_id (doesn't require project)
GET /api/docs/- Swagger UI documentation (includes authentication)GET /api/redoc/- ReDoc documentationGET /api/schema/- OpenAPI schema
The external_id is the artwork ID from Art Institute of Chicago API. You can get it in several ways:
GET /api/places/search-artworks/?query=monet&limit=10
Authorization: Bearer <your_token>This will return a list of artworks with their IDs. Use the id field as external_id.
GET https://api.artic.edu/api/v1/artworks/search?q=monet&limit=10Response includes artworks with id field - use this as external_id.
You can test with these valid IDs:
27992- Example artwork25865- Example artwork16571- Example artwork
Important: All requests require JWT token in the Authorization header:
Authorization: Bearer <your_access_token>
POST /api/projects/
Content-Type: application/json
Authorization: Bearer <your_token>
{
"name": "Trip to Chicago",
"description": "Exploring art museums",
"start_date": "2024-06-01"
}POST /api/projects/
Content-Type: application/json
Authorization: Bearer <your_token>
{
"name": "Trip to Chicago",
"description": "Exploring art museums",
"start_date": "2024-06-01",
"places_data": [
{
"external_id": "27992",
"notes": "Must see this artwork"
},
{
"external_id": "25865",
"notes": "Interesting piece"
}
]
}POST /api/projects/1/places/
Content-Type: application/json
Authorization: Bearer <your_token>
{
"external_id": "27992",
"notes": "Additional notes about this place"
}Note: The external_id must exist in Art Institute of Chicago API. Use /api/places/search-artworks/ to find valid IDs.
PATCH /api/projects/1/places/1/
Content-Type: application/json
Authorization: Bearer <your_token>
{
"notes": "Updated notes"
}Note: The 1 in /places/1/ is the place ID (not external_id). Get it from the list of places endpoint.
PATCH /api/projects/1/places/1/
Content-Type: application/json
Authorization: Bearer <your_token>
{
"is_visited": true
}When all places in a project are marked as visited, the project is automatically marked as completed.
- Place limit: A project can contain up to 10 places (can be created without places)
- Uniqueness: A place with the same
external_idcan be added to a project only once - Place validation: Before adding, a place is validated through Art Institute API
- Project deletion: A project cannot be deleted if at least one place is marked as visited
- Project completion: A project is automatically marked as completed when all places are visited
?is_completed=true- Only completed projects?is_completed=false- Only incomplete projects?start_date=2024-01-01- Projects with specific start date?search=chicago- Search by name or description?ordering=-created_at- Sorting (default: newest first)
Run tests:
cd config
python manage.py testNote: Tests use JWT authentication. A test user is automatically created in setUp() method with JWT token for each test case.
TravelProject/
├── app/
│ ├── projects/ # Projects app
│ │ ├── models.py # Project model
│ │ ├── serializers.py # Project serializers
│ │ ├── views.py # ProjectViewSet, UserRegistrationView
│ │ ├── urls.py # Project routes
│ │ ├── validators.py # Business logic validators
│ │ ├── auth_serializers.py # User registration serializer
│ │ └── tests.py # Project tests
│ └── places/ # Places app
│ ├── models.py # ProjectPlace model
│ ├── serializers.py # Place serializers
│ ├── views.py # ProjectPlaceViewSet, SearchArtworksView
│ ├── urls.py # Place routes (nested)
│ ├── services.py # Art Institute API service
│ ├── validators.py # Place validators
│ └── tests.py # Place tests
├── config/ # Django settings
│ ├── config/
│ │ ├── settings.py # Django settings
│ │ ├── urls.py # Main URL configuration
│ │ ├── wsgi.py
│ │ └── asgi.py
│ └── manage.py
├── docker/ # Docker configuration
│ ├── Dockerfile
│ └── docker-compose.yml
├── static/ # Static files
├── media/ # Media files
├── README.md # Project documentation
├── requirements.txt # Python dependencies
└── Travel_Planner_API.postman_collection.json # Postman collection
- Django 5.2.11
- Django REST Framework 3.16.1
- Django REST Framework Simple JWT 5.3.1 (authentication)
- PostgreSQL (via psycopg2-binary)
- drf-spectacular 0.29.0 (OpenAPI documentation)
- django-filter 25.2 (filtering)
- djangorestframework-nested 0.1.6 (nested routes)
- requests 2.32.5 (HTTP client for external API)
- python-dotenv 1.2.1 (environment variables)
Import the Travel_Planner_API.postman_collection.json file into Postman to test all endpoints.
The collection includes:
- Authentication endpoints (register, token, refresh)
- All project endpoints with JWT authentication
- All place endpoints with JWT authentication
- Search artworks endpoint
- Automatic token management (tokens are saved to variables)
Setup:
- Import the collection
- Set
base_urlvariable tohttp://localhost:8000(or your server URL) - Run "Register User" or "Get Access Token" first
- Tokens will be automatically saved and used for subsequent requests
Required environment variables in .env file:
# Django Settings
SECRET_KEY=your-secret-key-here
DEBUG=True
ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0
# Database Settings
DB_NAME=travel_db
DB_USER=postgres
DB_PASSWORD=postgres
DB_HOST=localhost # Use 'db' for Docker
DB_PORT=5432This project was created for a test assignment.