A Django REST API for authenticated poll creation, conditional questions, validated one-time submissions, and creator-only result aggregation.
This repository currently contains the backend API only. The implementation is intentionally documented without production, scale, frontend, or performance claims.
- JWT access and refresh tokens.
- Public poll listing and retrieval.
- Authenticated poll creation with nested questions and options.
- Single-choice, multiple-choice, and text questions.
- One optional conditional rule per question, using stable UUID references inside the create request.
- Creator-only poll updates, deletion, and result access.
- Authenticated respondent submissions with strict question and option scoping.
- Conditional visibility and required-question validation before any answer is written.
- One database-enforced submission per user and poll.
- Transactional answer creation and aggregated choice/text results.
- Automated checks for dependencies, Django configuration, migrations, and tests.
The submission service resolves questions only from the requested poll and options only from their parent question. It rejects duplicate questions, duplicate options, hidden answers, missing required visible questions, and invalid answer shapes before creating a Submission or Answer.
A Submission connects one user to one poll. Database constraints enforce one submission per user and one answer per question within that submission. The write path also uses transaction.atomic() and locks the poll row before validation and persistence.
Nested create requests may provide UUIDs for questions and options. A conditional question refers to those IDs, allowing all questions and options to be created first and conditions to be validated afterward in the same transaction. Self-dependencies, cross-poll references, mismatched trigger options, and dependency cycles are rejected.
The application will not start without DJANGO_SECRET_KEY. Debug mode defaults to false, allowed hosts and CORS origins are explicit lists, and the current .gitignore excludes new environment files and local SQLite data. The development key removed from the original code must remain permanently unused.
User ── creates ──> Poll ── contains ──> Question ── offers ──> Option
│ │ │
└── submits ──> Submission ── contains ──> Answer
ConditionalLogic:
target question ── depends on ──> parent question + trigger option
| Method | Route | Access | Behavior |
|---|---|---|---|
POST |
/api/auth/token/ |
Public | Obtain JWT access and refresh tokens |
POST |
/api/auth/token/refresh/ |
Public | Obtain a new access token |
GET |
/api/polls/ |
Public | List polls with pagination |
POST |
/api/polls/ |
Authenticated | Create a poll with nested questions and options |
GET |
/api/polls/{poll_id}/ |
Public | Retrieve a poll definition |
PUT / PATCH |
/api/polls/{poll_id}/ |
Creator | Replace or partially update title and description |
DELETE |
/api/polls/{poll_id}/ |
Creator | Delete the poll |
POST |
/api/polls/{poll_id}/answers/ |
Authenticated | Submit one validated response |
GET |
/api/polls/{poll_id}/results/ |
Creator | Read aggregated results |
Send authenticated requests with Authorization: Bearer <access_token>.
The CI workflow tests Python 3.12. Local verification has also passed on Python 3.13.
git clone https://github.com/ahmed-elsayed-programmer/smart-polling.git
cd smart-polling
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r backend/requirements.txtGenerate a local secret, then export it together with the local development settings:
python -c "from secrets import token_urlsafe; print(token_urlsafe(50))"
export DJANGO_SECRET_KEY="paste-the-generated-value-here"
export DJANGO_DEBUG=true
export DJANGO_ALLOWED_HOSTS="localhost,127.0.0.1"
export DJANGO_CORS_ALLOWED_ORIGINS="http://localhost:3000,http://127.0.0.1:3000"Prepare the database and start the API:
python backend/manage.py migrate
python backend/manage.py createsuperuser
python backend/manage.py runserverThe API is available at http://127.0.0.1:8000/api/.
Obtain a token:
curl -X POST http://127.0.0.1:8000/api/auth/token/ \
-H "Content-Type: application/json" \
-d '{"username":"creator","password":"your-password"}'Create a conditional poll. The client-generated IDs make the dependency references stable inside one request:
{
"title": "Developer survey",
"description": "A small conditional poll",
"questions": [
{
"id": "11111111-1111-4111-8111-111111111111",
"text": "Do you use Django?",
"type": "single-choice",
"required": true,
"options": [
{
"id": "22222222-2222-4222-8222-222222222222",
"text": "Yes"
},
{
"id": "33333333-3333-4333-8333-333333333333",
"text": "No"
}
]
},
{
"id": "44444444-4444-4444-8444-444444444444",
"text": "What do you build with Django?",
"type": "text",
"required": true,
"conditional_logic": {
"depends_on_question": "11111111-1111-4111-8111-111111111111",
"depends_on_option": "22222222-2222-4222-8222-222222222222"
}
}
]
}Submit answers:
{
"answers": [
{
"question_id": "11111111-1111-4111-8111-111111111111",
"selected_options": ["22222222-2222-4222-8222-222222222222"]
},
{
"question_id": "44444444-4444-4444-8444-444444444444",
"text_value": "REST APIs and business platforms"
}
]
}From the repository root with the virtual environment and DJANGO_SECRET_KEY set:
python -m pip check
cd backend
python manage.py check
python manage.py makemigrations --check --dry-run
python manage.py migrate --noinput
python manage.py test --verbosity 2- The repository contains the backend API only; it does not currently include a React or Next.js client.
- SQLite is configured for local development. No production database or deployment is demonstrated here.
- Result updates are REST-based; authenticated WebSocket delivery is not implemented.
- Poll definitions are publicly readable, while results are restricted to the creator.
- Conditional logic supports one option-triggered dependency per target question.
- There are no measured usage, reliability, or performance results for this project.
- Add a React or Next.js client in a separately testable frontend workspace.
- Define a production deployment using PostgreSQL and an environment-specific configuration.
- Add an authenticated real-time result channel with an explicit authorization policy.
- Add rate limiting, lifecycle controls, and export formats based on validated product requirements.