Django 기능 요약

Tony Lee·2023년 4월 28일
0

knowledge

목록 보기
4/16
post-thumbnail

기본적인 django 프로젝트 구성을 이해한 상태로 가정함.

Shell

$ python3 manage.py shell

filters

>>> from polls.models import *

get()

# objects.get() only returns one, else error
# get() could be useful for searching primaryKey values
>>> q = Question.objects.get(question___startswith="SEARCH_STRING")

filter()

# 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.

order_by()

>>> 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

HTML

variables

{{ VARIABLE }}

They are usually passed in as context

Python code

{% 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".

HTTP

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})

Forms

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'))

Reverse vs Redirect vs Reverse_lazy

Redirect Method will redirect you to a specific route in General. Reverse Method will return the complete URL to that route as a String.

Reverse

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.

Redirect

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

Reverse_lazy

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.

F()

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.

Serializers

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

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

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.

API 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,

  • get() method will return all serialized questions.
  • post() method will save request.data

Mixins

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()

Concrete View Classes

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.

Users

Retrieve Users via API

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.

Add Users via Forms

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>

End

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.

profile
Striving to have a positive impact on the community

0개의 댓글