기본적인 django 프로젝트 구성을 이해한 상태로 가정함.
$ python3 manage.py shell
>>> from polls.models import *
# objects.get() only returns one, else error
# get() could be useful for searching primaryKey values
>>> q = Question.objects.get(question___startswith="SEARCH_STRING")
# for multiple objects, use filter
>>> Question.objects.filter(pub_date__year=2023)
<QuerySet [<Question: 제목: 휴가를 어디서 보내고 싶으세요?, 날짜: 2023-02-05 18:52:59+00:00>, <Question: 제목: 가장 좋아하는 디저트는?, 날짜: 2023-02-05 18:53:27+00:00>, ...]>
# filter has multiple dunder expressions
# __contains, __regex, __gt, etc.
>>> Question.objects.order_by('-pub_date')[:5]
>>> print(Question.objects.order_by('-pub_date')[:5].query)
SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" ORDER BY "polls_question"."pub_date" DESC LIMIT 5
{{ VARIABLE }}
They are usually passed in as context
{% PYTHON_CODE %}
When using python code, the shell scripts also work the same way.
<h1>{{ question.question_text }}</h1>
# for all choice in question, print choice text.
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
polls/urls.py
...
app_name = 'polls'
urlpatterns = [
path('', views.index, name='index'),
path('<int:question_id>/', views.detail, name='detail'),
]
polls/templates/polls/index.html
<a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a>
The href is interpreted as polls/detail
and question.id
is passed in as the <int:question_id>/
.
polls:detail
means, go to polls app and find the name "detail".
from django.http import HttpResponse
from django.http import Http404
from django.shortcuts import render , get_object_or_404
...
def detail(request, question_id):
"""
try:
question = Question.objects.get(pk=question_id)
except Question.DoesNotExist:
raise Http404("Question does not exist")
"""
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/detail.html', {'question': question})
polls/templates/polls/detail.html
<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
<h1>{{ question.question_text }}</h1>
{% if error_message %}
<p><strong>{{ error_message }}</strong></p>
{% endif %}
{% for choice in question.choice_set.all %}
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
<label for="choice{{ forloop.counter }}">
{{ choice.choice_text }}
</label>
<br>
{% endfor %}
<input type="submit" value="Vote">
</form>
method="post"
calls polls:vote
, with question.id and request as input variables.
vote
checks if the object is valid and saves the vote count.
polls/urls.py
from django.urls import path
from . import views
app_name = 'polls'
urlpatterns = [
path('', views.index, name='index'),
path('<int:question_id>/', views.detail, name='detail'),
path('<int:question_id>/vote/', views.vote, name='vote'),
]
polls/views.py
...
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
try:
selected_choice = question.choice_set.get(pk=request.POST['choice'])
except (KeyError, Choice.DoesNotExist):
return render(request, 'polls/detail.html', {'question': question, 'error_message': '선택이 없습니다.'})
else:
selected_choice.votes += 1
selected_choice.save()
return HttpResponseRedirect(reverse('polls:index'))
Redirect Method will redirect you to a specific route in General. Reverse Method will return the complete URL to that route as a String.
The reverse() function returns a string - the URL referenced by the given name in Django urlpatterns
.
$ reverse('admin:app_list', kwargs={'app_label': 'auth'})
>>> '/admin/auth/'
So then we can combine it with HttpResponseRedirect
.
args and kwargs cannot be passed to reverse() at the same time.
The redirect() function returns an HttpResponse object with a status code 302.
As written in the docs, depending on the given arguement, it'll call different functions.
Return an HttpResponseRedirect to the appropriate URL for the arguments
passed.
The arguments could be:
* A model: the model's `get_absolute_url()` function will be called.
* A view name, possibly with arguments: `urls.reverse()` will be used
to reverse-resolve the name.
* A URL, which will be used as-is for the redirect location.
Issues a temporary redirect by default; pass permanent=True to issue a
permanent redirect.
-- from docs
In short, reverse_lazy() is used for CBV(Class Based Views), because class has to be loaded before the redirect and reverse() can be used in FBV(Function Based Views).
Another difference to note
reverse() returns a string & reverse_lazy() returns an object
It is useful for when you need to use a URL reversal before your project’s URLConf is loaded.
Some common cases where this function is necessary are:
* providing a reversed URL as the url attribute of a generic class-based view.
* providing a reversed URL to a decorator (such as the login_url argument for the django.contrib.auth.decorators.permission_required() decorator).
* providing a reversed URL as a default value for a parameter in a function’s signature.
from django.db.models import F
F() is a useful function when manipulating data on db from python code.
Highly recommend reading the docs for this function.
reporter = Reporters.objects.get(name="Tintin")
reporter.stories_filed += 1
reporter.save()
# Equivalent code
reporter = Reporters.objects.get(name="Tintin")
reporter.stories_filed = F("stories_filed") + 1
reporter.save()
To access the new value saved this way, the object must be reloaded:
reporter.refresh_from_db()
If the db is not refreshed, it could end up in confusing results.
from rest_framework import serializers
Serializers useful for buidling an API endpoint as it converts Python objects to JSON.
Serializers in Django REST Framework are responsible for converting objects into data types understandable by javascript and front-end frameworks.
polls_api/serializers.py
class QuestionSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
question_text = serializers.CharField(max_length=200)
pub_date = serializers.DateTimeField(read_only=True)
This can later be linked to polls_api/views.py
ModelSerializers are the equivalent of serializers with an automation of reading the model.
# Compare this code with the above code.
class QuestionSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source="owner.username")
choices = ChoiceSerializer(many=True, read_only=True)
class Meta:
model = Question
fields = ["id", "question_text", "pub_date", "owner", 'choices']
In the above example, Meta class is defined with models.Question
and below are the fields.
owner
and choices
have been overwritten for pre-populated fields and read_only fields.
Overall, ModelSerializers help the user to create serializers.
Generic views take care of repeated views on the web.
If the generic views don't suit the needs of your API, you can drop down to using the regular APIView
class, or reuse the mixins
and base classes used by the generic views to compose your own set of reusable generic views.
from rest_framework.views import APIView
Django rest framework provides a defaul API View.
class QuestionList(APIView):
def get(self, request):
questions = Question.objects.all()
serializer = QuestionSerializer(questions, many=True)
return Response(serializer.data)
def post(self, request):
serializer = QuestionSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Which needs to be defined with each response method.
In layman terms,
Mixins are the build-blocks of generic views.
from rest_framework import mixins
from rest_framework import generics
class QuestionList(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
This code will simply return question list for get()
,
and create new serialized data for post()
from polls.models import Question
from polls_api.serializers import QuestionSerializer
from rest_framework import generics
class QuestionList(generics.ListCreateAPIView):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
If you don't need much customizations, Concrete View Classes can do the job.
As the name implies, it's a pre-made class veiw that don't require many variables.
Add owner field in polls/models.py
class Question(models.Model):
question_text = models.CharField(max_length=200, verbose_name='질문')
pub_date = models.DateTimeField(auto_now_add=True, verbose_name='생성일')
owner = models.ForeignKey('auth.User', related_name='questions', on_delete=models.CASCADE, null=True)
Setup serializer polls_api/serializers.py
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
questions = serializers.PrimaryKeyRelatedField(many=True, queryset=Question.objects.all())
class Meta:
model = User
fields = ['id', 'username', 'questions']
Setup views polls_api/views.py
from django.contrib.auth.models import User
from polls_api.serializers import UserSerializer
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
Setup URLs polls_api/urls.py
from django.urls import path
from .views import *
urlpatterns = [
path('question/', QuestionList.as_view(), name='question-list'),
path('question/<int:pk>/', QuestionDetail.as_view()),
path('users/', UserList.as_view(),name='user-list'),
path('users/<int:pk>/', UserDetail.as_view()),
]
Now localhost/users/
and localhost/users/pk/
will display list of users and detail correspondingly.
polls/view.py
from django.views import generic
from django.urls import reverse_lazy
from django.contrib.auth.forms import UserCreationForm
class SignupView(generic.CreateView):
form_class = UserCreationForm
success_url = reverse_lazy('user-list')
template_name = 'registration/signup.html'
UserCreationForm is a default form table from django.
As mentioned above, reverse_lazy('user-list')
=> '/rest/users/'
urlpatterns = [ ...,
path('users/', UserList.as_view(), name="user-list"),
]
polls/templates/registration/signup.html
<h2>회원가입</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">가입하기</button>
</form>
These are some of the basic functions I've learned throughout the week and this doc will stay as my reference point for future projects.