Reproduction step-by-step of this course "Python crash course"
-
Install pipenv
sudo -H pip3 install pipenv
-
Create pipenv dependencies file
pipenv shell
-
Install django in environment
pipenv install django
-
Create django pollster project
django-admin startproject pollster
These files are created
- pollster/
- asgi.py
- settings.py: contains django configuration (production secret key, database, tzconfig...)
- urls.py: contains url managed by the application
- wsgi.py
- pollster/
-
Run installed django environment (for testing)
cd pollster/ && python3 manage.py runserver
-
Run migrations (to create default database)
python3 manage.py migrate
-
Create app (polls)
python3 manage.py startapp polls
-
Add polls to apps
move to
pollster/pollster/settings.py
and add toINSTALLED_APPS
the entry'polls.apps.PollsConfig'
We're then going to define the model for our poll applications. These entities will then be created into the database, when running the migration.
-
move to
pollster/polls/models.py
-
Define models
class Question(models.Model): """ Question represents a poll """ question_text = models.CharField(max_length=256) pub_date = models.DateTimeField("publication date") def __str__(self) -> str: return self.question_text class Choice(models.Model): """ Choice represents a choice for a certain ``Question`` """ # If a question is deleted, all related choices are deleted -> `on_delete = models.CASCADE` question = models.ForeignKey(Question, on_delete=models.CASCADE) choice_text = models.CharField(max_length=256) votes = models.IntegerField(default=0) def __str__(self) -> str: return self.choice_text
-
Make migrations
run
python manage.py makemigrations polls
-
Run migrations
as before
python3 manage.py migrate
-
Run shell and add a new Question entity
python manage.py shell >>> from django.utils import timezone >>> from polls.models import Question, Choice >>> q = Question(question_text = "What is your favourite Programming language?", pub_date=timezone.now()) >>> q.save() >>> q.id >>> q.question_text >>> Questions.objects.all()
-
Add a new Choice
python manage.py shell >>> q = Question.objects.get(pk = 1) >>> q.choices.create(choice_text = "C/C++") >>> q.choices.create(choice_text = "JavaScript") >>> q.choices.create(choice_text = "Rust") >>> q.choices.create(choice_text = "Python") >>> q.choices.create(choice_text = "Java") >>> q.choices.create(choice_text = "CSharp") >>> q.choices.create(choice_text = "Go") >>> q.choices.create(choice_text = "PHP") >>> q.choices.create(choice_text = "Ruby") >>> q.save() >>> quit()
-
Create super user
python manage.py createsuperuser Username (leave blank to use '${USER}'): Email address: christian.visintin@********* Password: Password (again): This password is too short. It must contain at least 8 characters. Bypass password validation and create user anyway? [y/N]: y Superuser created successfully.
-
Run server
python manage.py runserver
-
Go to admin page
from your browser:
http://localhost:8000/admin
-
Access with your credentials
-
Move to
pollster/polls/admin.py
-
Let's add models
from .models import Question, Choice admin.site.register(Question) admin.site.register(Choice)
Now you're finally able to add Questions and choices to the database.
-
Let's make it cooler
# Register your models here. from .models import Question, Choice class ChoiceInline(admin.TabularInline): model = Choice extra = 3 # Default choices entries class QuestionAdmin(admin.ModelAdmin): fieldsets = [ (None, {'fields': ['question_text']}), ('Date Information', {'fields': ['pub_date'], 'classes': ['collapse']}), ] inlines = [ChoiceInline] #admin.site.register(Question) #admin.site.register(Choice) admin.site.register(Question, QuestionAdmin)
-
(Extra) add header and title for administration
# Set website header admin.site.site_header = "Pollster Admin" admin.site.site_title = "Pollster Admin Area" admin.site.index_title = "Welcome to Pollster administration"
-
Move to
pollster/polls/views.py
-
Show questions
-
Add index definition
from .models import Question, Choice # Get questions and display then def index(request): return render(request, 'polls/index.html')
-
Define url
Create
pollster/polls/urls.py
and typefrom django.urls import path # Import views from . import views # Give app a name app_name = "polls" urlpatterns = [ path('', views.index, name='index') # /polls/index.html ]
-
Add polls url to pollster urls
Move to
pollster/pollster/urls.py
and add change
urlpatterns
to:# Add include from django.urls import path, include # ... # Add here urlpatterns = [ path('admin/', admin.site.urls), path('polls/', include('polls.urls')), ]
-
Add templates folder in
pollster/templates
-
Add index.html to
pollster/templates/polls/index.html
-
Set templates as global folder
move to
pollster/pollster/settings.py
and toTEMPLATES
add'DIRS': [os.path.join(BASE_DIR, 'templates')],
-
Let's make base html template at
pollster/templates/base.html
-
Extend base in
pollster/templates/polls/index.html
{% extends 'base.html' %} {% block content %} POLLS {% endblock %}
-
Move to
pollster/polls/views.py
def index(request): latest_question_list = Question.objects.order_by('-pub_date')[:5] # Max 5 and sort by pub date context = {'latest_question_list': latest_question_list} return render(request, 'polls/index.html', context=context)
-
Add implementation for
index.html
{% extends 'base.html' %} {% block content %} <h1 class="text-center mb-3">Poll questions</h1> {% if latest_question_list %} {% for question in latest_question_list %} <div class="card mb-3"> <div class="card-body"> <h2 class="lead"> {{ question.question_text }} </h2> <a href="{% url 'polls:detail' question.id %}" class="btn btn-primary btn-sm">Vote now!</a> <a href="{% url 'polls:results' question.id %}" class="btn btn-secondary btn-sm">View results</a> </div> </div> {% endfor %} {% else %} <h2 class="text-center mb-3">No polls available</h2> {% endif %} {% endblock %}
-
Let's implement details
Move to
pollster/polls/views.py
def detail(request, question_id): """ Show speicific question and choices """ try: # Get question by id question = Question.objects.get(pk=question_id) except Question.DoesNotExist: # Return 404 raise Http404 context = {'question': question} return render(request, 'polls/detail.html', context=context)
-
Let's implement results
def results(request, question_id: int): """ Show results for a question """ question = get_object_or_404(Question, pk=question_id) context = {'question': question} return render(request, 'polls/results.html', context=context)
-
Add other pages to
pollster/polls/urls.py
urlpatterns = [ path('', views.index, name='index'), # /polls/index.html path('<int:question_id>', view=views.detail, name='detail'), path('<int:question_id>', view=views.results, name='results'), path('<int:question_id>/vote/', views.vote, name='vote'), ]
-
Add results.html and detail.html
-
Add vote
def vote(request, question_id: int): """ Enregister the user vote """ question = get_object_or_404(Question, pk=question_id) try: selected_choice = question.choices.get(pk=request.POST['choice']) except (KeyError, Choice.DoesNotExist): return render(request, 'polls/detail.html', { 'question': question, 'error_message': "You didn't select any choice" }) else: # Increase choice counter and save selected_choice.votes += 1 selected_choice.save() # Return results return HttpResponseRedirect(reverse('polls:results', args=(question_id,)))
This part is not included in the django crash course video, but I included since I found it useful.
Let's create a REST API to interact with the poll application. We'll implement the following methods
- GET /polls/api/questions
which will return an object with all questions summary
[
{
"id": 1,
"text": "What's your favourite programming language?"
}
]
- GET /polls/api/question/{ID}
which will return an object with the question details and choices
[
{
"id": 1,
"text": "What's your favourite programming language?",
"choices": [
{
"id": 1,
"text": "Rust",
"votes": 2
},
{
"id": 2,
"text": "C/C++",
"votes": 1
}
]
}
]
- POST /polls/api/vote/{QUESTION_ID}
which will enregister the vote for a certain poll
{
"choice": 2
}
Let's see the implementation step-by-step.
Setup:
-
Install Django REST Framework
pip install djangorestframework
-
Tell Django we've installed the REST framework
Move to
pollster/pollster/settings.py
INSTALLED_APPS = [ #... 'rest_framework', ]
Let's create the GET request
-
Add
related_name
toChoice
in models:Move to
pollster/polls/models.py
class Choice(models.Model): """ Choice represents a choice for a certain ``Question`` """ # If a question is deleted, all related choices are deleted -> `on_delete = models.CASCADE` question = models.ForeignKey(Question, related_name='choices', on_delete=models.CASCADE) #...
-
Create serializers
move to
pollster/polls/serializers.py
# Import serializers from REST framework from rest_framework import serializers # Import our models from .models import Question, Choice class ChoiceSerializer(serializers.ModelSerializer): class Meta: model = Choice fields = ('choice_text', 'votes', 'id') class QuestionBriefSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Question fields = ('question_text', 'pub_date') class QuestionSerializer(serializers.HyperlinkedModelSerializer): # Add choices to relations and serialize it using the `ChoiceSerializer` choices = ChoiceSerializer(many=True) class Meta: model = Question fields = ('question_text', 'pub_date', 'choices')
-
Display data in views
move to
pollster/polls/views.py
from rest_framework.response import Response from rest_framework.decorators import api_view from .serializers import QuestionSerializer, QuestionBriefSerializer #... @api_view(['GET']) def questionList(request): questions = Question.objects.all().order_by('pub_date') serializer = QuestionBriefSerializer(questions, many=True) # Many indicates whether we want 1 or more elements return Response(serializer.data) @api_view(['GET']) def questionDetail(request, question_id): question = get_object_or_404(Question, pk=question_id) serializer = QuestionSerializer(question, many=False) # Many indicates whether we want 1 or more elements return Response(serializer.data)
-
Add URLs and router
Move to
pollster/polls/urls.py
from django.urls import path # Import views from . import views # Give app a name app_name = "polls" urlpatterns = [ path('', views.index, name='index'), # /polls/index.html path('<int:question_id>/', views.detail, name='detail'), path('<int:question_id>/results', views.results, name='results'), path('<int:question_id>/vote/', views.vote, name='vote'), path('api/questions/', views.questionList, name='questions'), path('api/question/<int:question_id>/', views.questionDetail, name="question"), ]
Now you should finally be able to perform a GET to http://localhost:8000/polls/api/questions
Let's implement the vote POST method.
-
Add request to view
@api_view(['POST']) @parser_classes([JSONParser]) def questionVote(request, question_id): question = get_object_or_404(Question, pk=question_id) print(request.data) try: selected_choice = question.choices.get(pk=request.data['choice']) except (KeyError, Choice.DoesNotExist): raise Http404 else: # Increase choice counter and save selected_choice.votes += 1 selected_choice.save() # Return updated question serializer = QuestionSerializer(question, many=False) # Many indicates whether we want 1 or more elements return Response(serializer.data)
-
Add URL
path('api/vote/<int:question_id>', views.questionVote, name="vote"),
And that's all.