https://blueshw.github.io/2016/03/03/django-using-custom-templatetags/
[django] 커스텀 템플릿태그(templatetags) 활용하기


웹 개발을 하다보면, html 코드 상에서 다양한 연산을 해야하는 경우가 발생합니다. 그래서 php, jsp, asp, jade 등 각 언어별 웹 프레임워크에서 이와 같은 경우를 처리해주기 위한 기능을 제공하고 있습니다. 장고(django) 템플릿(template)에서도 위와 같은 웹 프레임워크와 같이 동일한 기능을 지원하는 템플릿태그(templatetags)라는 것이 있습니다. 장고의 템플릿태그는 다른 웹 프레임워크와 마찬가지로 기본적으로 개발자가 필요한 기능은 대부분 제공하고 있습니다.

웹 프레임워크가 기본적인 기능을 대부분 제공하고 있지만, 개발을 하다보면 자신이 원하는 기능이 없는 경우가 간혹 있습니다. 그래서 장고에서는 개발자가 커스텀으로 템플릿태그를 만들수 있는 기능을 제공하고 있습니다.

우선 아래와 같이 앱(app) 아래에 templatetags 라는 폴더를 만들어 줍니다. temaplatetags 라는 폴더 이름은 고정값이므로 반드시 동일하게 생성합니다.

proj/
	app/
		__init__.py
		models.py
		view.py
		templatetags/
			__init__.py
			custom_tags.py    (커스텀 템플릿태그를 저장할 모듈 파일)

이때, app 은 반드시 setting 파일의 INSTALLED_APPS 에 추가가 되어 있어야 합니다. 그리고 한가지 주의할 점은 여러 앱에 각각 templatetags 가 있는 경우, 모듈의 이름이 겹치지 않도록해야 합니다. 이유는, template 에서 커스텀 태그는 앱의 위치와 상관없이 모듈 이름으로 로드되므로 이름이 겹치게 되면 충돌이 발생하게 됩니다. 즉,

proj/
	app/
		templatetags/
			custom_tags.py
	common/
		templatetags/
			common_tags.py    (태그모듈 이름이 겹치지 않도록 주의!!!)

태그 모듈을 사용하는 방법은 간단합니다. 커스텀 태그를 사용하고자하는 템플릿 파일의 상단에 아래와 같이 한줄만 추가해주면 됩니다.

{% load custom_tags %}

그렇다면 실제로 커스텀 태그를 만들어서 사용하는 예제를 만들어 보도록 하겠습니다. custom_tags.py 파일을 열어 사용하려는 태그 이름으로 메서드 이름으로 지정하여 만들어줍니다.

@register.filter            # 1
def add_str(left, right):
return left + right

@register.simple_tag            # 2
def today():
return datetime.now().strftime("%Y-%m-%d %H:%M")

@register.assignment_tag            # 3
def max_int(a, b):
return max(int(a), int(b))

@register.inclusion_tag(div.html, takes_context=True)            # 4
def include_div(context):
return {
'div_param': context['param']
}

대략 위의 4 가지 태그로 구분할 수 있는데, 각 태그에 따라서 사용법이 조금씩 다릅니다. 이 4 가지 커스텀 태그만 이용하면 웬만한 기능은 다 만들어 낼 수 있습니다. 번호별 사용법은 아래와 같습니다.

# 1.
# filter 태그는 앞의 값(left)에다가 뒤의 값(right)을 연산하는 태그입니다.
# filter이기 때문에 여러개의 필터를 붙여서 사용가능합니다.
# add_str 메서드의 left 파라미터가 prefix에 해당하고, right 파라미터가 url에 해당합니다.
# 결과적으로 prefix + url이 add_str 메서드를 통해 div의 text가 되는 것이지요.

<div>
{{ prefix | add_str: url | add_str: name | add_str: params }}
</div>

# 2.
# simple_tag는 단순히 어떤 특정값을 출력합니다.
# 아래와 같이 today를 입력하면, "2016-3-2 10:00"과 같이 현재 시간이 출력됩니다.

<div>
{{ today }}
</div>

# 3.
# assignment_tag는 템플릿에서 사용가능한 변수에 결과를 저장하는 역할을 합니다.
# 어찌보면 with 태그와 유사한 형태라 할 수 있으나, with과는 다르게 {% endwith %} 처럼 끝을 맺어줄 필요가 없습니다.
# 즉, 좀 더 간편하게 변수를 설정해 줄 수 있고, 필요한 기능을 태그 모듈에 별도로 삽입할 수 있다는 장점이 있습니다.

{% max_int first_count second_count as max_count %}

# 4.
# inclusion_tag는 저도 프로젝트에 직접 사용해 보진 않았지만, 테스트는 해보았습니다.
# 간략히 설명해서 inclusion_tag를 사용하면 데코레이터의 첫번째 파라미터인 템플릿을 호출하여 부모 템플릿에 출력합니다.
# 이때, 호출되는 템플릿에 부모 템플릿(호출하는 템플릿)의 각종 파라미터를 전달해 줄 수 있습니다.
# 데코레이터의 takes_context=True로 설정해주면,
# 부모 템플릿의 context의 값을 가져와 호출하는 템플릿으로 전달할 수 있습니다.

parent.html
{{ include_div }}

div.html
<div>{{ div_param }}</div>

이상 커스텀 태그의 종류와 사용법에 대해서 알아보았습니다. 제가 설명드린 커스텀 태그는 아주 기초적인 부분이라 제작 및 사용법이 아주 간단한데요. 커스텀 태그 파일은 파이썬 모듈이기 때문에 파이썬에서 사용할 수 있는 내장함수와 모든 확장 모듈을 사용할 수 있기 때문에, 얼마든지 복잡하고 파워풀한 기능을 가진 태그를 만들어 낼 수 있습니다.

하지만, 복잡한 연산을 처리하는 것은 템플릿보다는 웹서버 단에서 처리하는 것이 우선이고, 서버에서 처리가 곤란하거나 불가피한 상황인 경우에 태그를 사용해서 처리하는 것이라 생각합니다. 아마 장고에서도 사용가능한 기본 태그를 최소한으로 만들어 놓은 것도 같은 이유 때문일 거라 생각이 드네요.

https://wayhome25.github.io/django/2017/05/05/django-url-reverse/
django official : https://docs.djangoproject.com/en/2.2/ref/urlresolvers/



  • 장고는 urls.py 변경을 통해 ‘각 뷰에 대한’ url이 변경되는 유연한 시스템을 갖고 있다.
  • url이 변경 되더라도 url name명만 일치하면  url reverse가 변경된 url을 추적한다. (누락의 위험이 적다)

URL Reverse를 수행하는 4가지 함수

reverse()

  • 리턴형식 : string
from django.core.urlresolvers import reverse

reverse('blog:post_list') # '/blog/'
reverse('blog:post_detail', args=[10]) # '/blog/10/' args 인자로 리스트 지정 필요
reverse('blog:post_detail', kwargs={'id':10}) # '/blog/10/'
reverse('/hello/') # NoReverseMatch 오류 발생

resolve_url()

  • 리턴형식 : string
  • 내부적으로 reverse() 사용
  • reverse() 보다 사용이 간단하다.
from django.shortcuts import resolve_url

resolve_url('blog:post_list') # '/blog/'
resolve_url('blog:post_detail', 10) # '/blog/10/'
resolve_url('blog:post_detail', id=10) # '/blog/10/'
resolve_url('/hello/') # '/hello/' 문자열 그대로 리턴


본글 : https://naruport.com/blog/2019/7/25/django-custom-form/
같이보면 좋은 자료 : https://developer.mozilla.org/ko/docs/Learn/Server-side/Django/Forms



煉獄!Djangoのフォームを俺好みに改造する

65, 2019-07-25
Djangoロゴ

目次

はじめに

以下のようなフォームがある。

改造前のフォーム

# forms from django import forms class ProfileForm(forms.Form): name = forms.CharField() age = forms.IntegerField()

# templates <form action="{% url 'index' %}" method="POST"> {% csrf_token %} {{ profile_form }} <input type="submit" value="送信"> </form>

# view

from django.http import HttpResponseRedirect from django.shortcuts import render from .forms import NameForm def get_name(request): # if this is a POST request we need to process the form data if request.method == 'POST': # create a form instance and populate it with data from the request: form = NameForm(request.POST) # check whether it's valid: if form.is_valid(): # process the data in form.cleaned_data as required # ... # redirect to a new URL: return HttpResponseRedirect('/thanks/') # if a GET (or any other method) we'll create a blank form else: form = NameForm() return render(request, 'name.html', {'profile_form ': form})



これを改造していく。

Formの多様なフォーマット
 BooleanFieldCharFieldChoiceFieldTypedChoiceFieldDateFieldDateTimeFieldDecimalFieldDurationFieldEmailFieldFileFieldFilePathFieldFloatFieldImageFieldIntegerFieldGenericIPAddressFieldMultipleChoiceFieldTypedMultipleChoiceFieldNullBooleanFieldRegexFieldSlugFieldTimeFieldURLFieldUUIDFieldComboFieldMultiValueFieldSplitDateTimeFieldModelMultipleChoiceFieldModelChoiceField .
(
https://developer.mozilla.org/ko/docs/Learn/Server-side/Django/Forms)

フィールドを参照する

テンプレート内でフォームのフィールド名を参照すると対応するフィールドが取り出せる。

from django import forms


class ProfileForm(forms.Form):
    name = forms.CharField()
    age = forms.IntegerField()
{% with profile_form as f %}
<form action="{% url 'index' %}" method="POST">
    {% csrf_token %}
    <div>{{ f.name }}</div>
    <div>{{ f.age }}</div>
    <input type="submit" value="送信">
</form>
{% endwith %}

しかしインプット要素のみの取り出しなので味気がない出力になる。

ラベルを参照する

ラベル名を参照するにはフィールドのlabelを参照する。ちなみにlabel名の参照ではHTMLlabelは生成されないので、別途タグで囲む必要がある。

{% with profile_form as f %}
<form action="{% url 'index' %}" method="POST">
    {% csrf_token %}
    <div><label>{{ f.name.label }}</label>: {{ f.name }}</div>
    <div><label>{{ f.age.label }}</label>: {{ f.age }}</div>
    <input type="submit" value="送信">
</form>
{% endwith %}

ラベル名を変更するにはフォームのフィールドのlabelを指定する。

from django import forms


class ProfileForm(forms.Form):
    name = forms.CharField(label='名前')
    age = forms.IntegerField(label='年齢')


대부분의 필드에 공통적인 인자들은 아래와 같다. ( 이들은 적절한 기본값을 가지고 있다 ):

  • requiredTrue 로 설정되면, 필드를 빈칸으로 두거나 None 값을 줄 수 없게된다. 보통필드는 required는 True로 기본 설정되므로, 폼에서 빈 칸을 허용하기 위해서는required=False 로 설정해야 한다. 
  • label: HTML에서 필드를 렌더링할때 사용하는 레이블이다. label 이 지정되지 않으면,  Django는 필드 이름에서 첫번째 문자를 대문자로, 밑줄을 공백으로 변형한 레이블을 새로 생성할 것이다. (예를 들면, renewal_date  --> Renewal date).
  • label_suffix: 기본적으로, 콜론(:)이 레이블 다음에 표시된다. (예를 들면, Renewal date:). 이 인자는 다른 문자(들)를 포함한 접미사를 지정할 수 있도록 해준다.
  • initial: 폼이 나타날 때 해당 필드의 초기 값.
  • widget: 사용할 디스플레이 위젯.
  • help_text (위의 예에서 봤듯이): 필드 사용법을 보여주는 추가적인 문구.
  • error_messages: 해당 필드의 에러 메시지 목록. 필요하면 문구를 수정할 수 있다.
  • validators: 해당 필드가 유효한 값을 가질 때 호출되는 함수의 목록.
  • localize: 폼 데이타 입력의 현지화(localisation)를 허용함 (자세한 정보는 해당 링크 참조).
  • disabled: 이 옵션이 True 일때 해당 필드를 볼 수는 있지만 편집이 안됨. 기본 값은 False.




出力結果。

ラベルを付けたフォーム

ヘルプの参照

フォームのフィールドにhelp_textを設定した場合、テンプレートではフィールドのhelp_textから設定文字列を参照できる。

from django import forms


class ProfileForm(forms.Form):
    name = forms.CharField(label='名前', help_text='英字のみ')
    age = forms.IntegerField(label='年齢', help_text='0以上120以下')
{% with profile_form as f %}
<form action="{% url 'index' %}" method="POST">
    {% csrf_token %}
    <div>
        <label>{{ f.name.label }}</label>:
        {{ f.name }}
        <p>{{ f.name.help_text }}</p>
    </div>
    <div>
        <label>{{ f.age.label }}</label>:
        {{ f.age }}
        <p>{{ f.age.help_text }}</p>
    </div>
    <input type="submit" value="送信">
</form>
{% endwith %}

出力結果。

ヘルプを付けたフォーム

エラーの参照

フォームは検証に失敗するとエラー文字列をフォームに格納する。

{% with profile_form as f %}
<form action="{% url 'index' %}" method="POST">
    {% csrf_token %}

    {# フィールドに属さない全体的なエラーの参照 #}
    {% if f.errors %}
        <p>{{ f.non_field_errors }}</p>
    {% endif %}

    <div>
        <label>{{ f.name.label }}</label>:
        {{ f.name }}
        <p>{{ f.name.help_text }}</p>
        {# フィールドに属しているエラーの参照 #}
        <p>{{ f.name.errors }}</p>
    </div>
    <div>
        <label>{{ f.age.label }}</label>:
        {{ f.age }}
        <p>{{ f.age.help_text }}</p>
        {# フィールドに属しているエラーの参照 #}
        <p>{{ f.age.errors }}</p>
    </div>
    <input type="submit" value="送信">
</form>
{% endwith %}

バリデーションの追加

バリデーションを追加するにはフォームにclean_で始まるメソッドを定義する(参照: http://python.keicode.com/django/form-validation.php)。
デフォルトのバリデーターが呼ばれた後に実行されるらしい。

from django import forms class ProfileForm(forms.Form): name = forms.CharField(max_length=20, label='名前', help_text='英字のみ') age = forms.IntegerField(label='年齢', help_text='0以上120以下') def clean_name(self): # viewのform.is_validが実行されるとdef clean_xxxx系の関数から有効性検査を行う name = self.cleaned_data['name'] isallalpha = True for c in name: if not c.isalpha(): isallalpha = False break if not isallalpha: raise forms.ValidationError('英字以外が含まれています。') return name def clean_age(self): """ ちなみにフィールドの属性 min_value, max_valueで入力範囲を制限できる。 それらを設定したらこのバリデーションは不要? """ age = self.cleaned_data['age'] if age < 0 or age > 120: raise forms.ValidationError('範囲外です。') return age

フォームに不正な値を入力してみた出力結果。

エラー表示を付けたフォーム

ちなみにテンプレートでerrorsを参照したい場合、views.pyでちょっとした工夫が必要になる。is_validを呼んだ後のフォームを再度出力用のテンプレートに渡すとerrorsを参照できるようになる。

from django.shortcuts import render
from django.http import HttpResponse
from . import forms


def index(req):
    if req.method == 'GET':
        return render(req, 'myapp/profile-form.html', {
            'profile_form': forms.ProfileForm(),
        })
    elif req.method == 'POST':
        profile_form = forms.ProfileForm(req.POST)
        if not profile_form.is_valid():
            # ここでerrorsを生かしたままテンプレートに渡す。
            return render(req, 'myapp/profile-form.html', {
                'profile_form': profile_form,
            })

        return HttpResponse('ok')

プレースホルダーの設定

インプット要素のプレースホルダーはフォームのwidgetで設定できる。widgetの種類は https://github.com/django/django/blob/master/django/forms/widgets.py を参照。

class ProfileForm(forms.Form):
    name = forms.CharField(max_length=20, label='名前', help_text='英字のみ', widget=forms.TextInput(attrs={
        'placeholder': 'Name',
    }))
    age = forms.IntegerField(min_value=0, max_value=120, label='年齢', help_text='0以上120以下', widget=forms.NumberInput(attrs={
        'placeholder': '0',
    }))

出力結果。

プレースフォルダーを付けたフォーム

だいぶフォームらしくなりました(・∀・)ノ


해당 어플리케이션 직하에/ templatetags 디렉토리 생성

예를 들어 example어플리케이션이 타겟이라면, application 디렉토리 아래에 templatetags디렉토리를 생성

example/
    __init__.py
    models.py
    templatetags/
        __init__.py
    views.py

template filter 스크립트 작성

위에서 생성한 templatetags 아래에 스크립트를 생성 후 저장
파일명은 무엇이든 상관 없으나, 이후 template에서 load할 때,파일명으로 불러오게 되므로 주의

(app_root)/templatetags/dict_filter.py

from django.template.defaulttags import register
...
@register.filter
def get_dict(dictionary, key):
    return dictionary.get(key)

customizing tag를 호출하기

커스터마이징 태그를 호출하기 위해서는 templatetags 아래있는 작성한 스크립트 명을 호출해야한다 : {% load dict_filter %}
이를 사용할 때는 함수명을 호출하여 사용한다 : {{ mydict|get_dict:item.NAME }}

test.html

{% load dict_filter %}
...
{{ mydict|get_dict:item.NAME }}
...

출처


https://ssungkang.tistory.com/entry/Django-render-%EC%99%80-redirect-%EC%9D%98-%EC%B0%A8%EC%9D%B4


[Django] render 와 redirect 의 차이(Django shortcut functions)

사용자 ssung.k 2019.05.21 17:49

HttpResponse

HttpResponse(data, content_type) 

 HttpResponse 는 HttpRequest와 짝을 이루며, response를 반환하는 가장 기본적인 함수입니다. express로 치자면 send 같은 놈이 되겠네요.  Json이나 html을 반환할 수도 있습니다.


HttpResponseRedirect

HttpResponseRedirect(url)

별다른 Response를 반환하는게 아니라, 지정된 url페이지로 redirect할시 사용됩니다.



render

render(request, template_name, context=None, content_type=None, status=None, using=None)

render 는 httpResponse객체를 반환하는 함수로, template을 context과 엮어 httpResponse로 쉽게 반환하게 해주는 함수입니다.

 다음과 같은 파라미터들을 가집니다. 이 중에서 request 와 template_name 은 필수적으로 필요합니다. request 는 위와 동일하게 써주게 되고, template_name 은 불러오고 싶은 템플릿을 기재해줍니다. 쉽게 생각해서 화면에 html 파일을 띄운다고 생각하면 됩니다. 이 때 context 로 원하는 인자를, 즉 view 에서 사용하던 파이썬 변수를 html 템플릿으로 넘길 수 있습니다. context 는 딕셔너리형으로 사용하며 key 값이 탬플릿에서 사용할 변수이름, value 값이 파이썬 변수가 됩니다.

# views.py

from django.shortcuts import render

def my_view(request):
    name = "minsung"
    return render(request, 'myapp/index.html', {
        'name': name,
    }

redirect

redirect(to, permanent=False, *args, **kwargs)

redirect 는 다음과 같은 파라미터를 가집니다. to 에는 어느 URL 로 이동할지를 정하게 됩니다. 이 때 상대 URL, 절대 URL 모두 가능하며 urls.py 에 name 을 정의하고 이를 많이 사용합니다. 단지 URL로 이동하는 것이기 때문에 render 처럼 context 값을 넘기지는 못합니다.

# views.py

from django.shortcuts import redirect

def my_view(request):
    ...
    return redirect('view-name')             # view_name 사용
    # return redirect('/some/url/')                  # 상대 경로 
      # return redirect('https://example.com/')# 절대 경로 

render 와 redirect 구분

두 함수를 헷갈려 혼동하는 경우가 많습니다. 특히 장고가 익숙하지 않을 때는 둘다 return 뒤에 위치하여 함수를 종료할 시 사용되니 그럴만 합니다. 생각 외로 둘의 차이는 명확합니다. render 는 템플릿을 불러오고, redirect 는 URL로 이동합니다. URL 로 이동한다는 건 그 URL 에 맞는 views 가 다시 실행될테고 여기서 render 를 할지 다시 redirect 할지 결정할 것 입니다. 이 점에 유의해서 사용하신다면 상황에 맞게 사용하실 수 있을 겁니다.


https://qiita.com/t-iguchi/items/54424f619829a6d135fc


Python + Djangoで独自のユーザ認証を実装する

この記事は最終更新日から1年以上が経過しています。

Djangoの認証機能を利用して簡単に認証機能を実装してみます

環境:
OS: Windows10 Home 64bit
Python 3.6.5
Django 2.0.6

Python + Djangoのプロジェクト作成でプロジェクトを作成します

1.migrate

python manage.py migrateを実行して、認証系のテーブルを作成します。

cmd.prompt
(venv) C:\data\python\work\myproject>python manage.py migrate

2.管理ユーザを作成する

管理ユーザを作成します。このユーザで認証を行うことになります。

cmd.prompt
python manage.py createsuperuser
Username (leave blank to use 'papa'): django
Email address: django@localhost
Password:
Password (again):
Superuser created successfully.

3.アプリケーションの構成

完成後のアプリケーションの構成は以下のとおりです
JQuery, Bootstrap4を使いますので、ダウンロードして配置しておきます

C:\data\python\work\myproject
├─accounts・・・・(1)
│  │  admin.py
│  │  apps.py
│  │  models.py
│  │  tests.py
│  │  urls.py・・・・(2)
│  │  views.py・・・・(3)
│  │  __init__.py
│  │  
│  ├─migrations
・・・
│  │          
│  └─__pycache__
・・・
├─myproject
│  │  settings.py・・・(8)
│  │  urls.py・・・・・(9)
│  │  wsgi.py
│  │  __init__.py
│  │  
│  └─__pycache__
・・・
├─static
│  ├─css
│  │      bootstrap.min.css
│  │      bootstrap.min.css.map
│  │      style.css・・・・(4)
│  └─js
│         bootstrap.bundle.min.js
│         jquery-3.3.1.min.js
└─templates
    ├─accounts
    │      top.html・・・・(5)
    ├─commons
    │      base.html・・・・(6)
    └─registration
           login.html・・・・(7)

(1)accounts

Djangoの認証機能を作成するには、accountsアプリケーションを作成します

cmd.prompt
(venv) C:\data\python\work\myproject>python manage.py startapp accounts

(2)accounts\urls.py

accounts\urls.pyを作成します。

accounts\urls.py
from django.urls import path
from . import views

app_name = 'accounts'
urlpatterns = [
    path('', views.index, name='index'),
]

(3)accounts\views.py

@login_required をつけると、このviewにアクセスするときに認証が必要になります

accounts\views.py
from django.shortcuts import render
from django.contrib.auth.decorators import login_required

@login_required
def index(request):
    return render(request, 'accounts/top.html')

(4)static\css\style.css

画面全体のスタイルを定義します。

static\css\style.css
@charset "UTF-8";

.mysystem-header{
  background: #3333FF !important;
  color: white !important;
}
.mysystem-border{
  background: #3333FF !important;
  color: white !important;
}

(5)accounts\top.html

認証後に表示するtop画面を作成します

accounts\top.html
{% extends "commons/base.html" %}

{% block title %}メニュー{% endblock %}

{% block styles %}
nav.menu ul li {
    list-style-type:none;
    font-size: 1.1em;
}
{% endblock %}

{% block headertitle %}メニュー画面{% endblock %}

{% block content %}
<nav class="menu">
  <ul>
      <li>
        <a target="_blank" href="/hello">Hello World!</a>
      </li>
  </ul>
</nav>
{% endblock %}

{% block scripts %}
<script>
</script>
{% endblock %}

(6)commons\base.html

共通テンプレートです

commons\base.html
{% load staticfiles %}
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap.min.css' %}">
    <link rel="stylesheet" type="text/css" href="{% static 'css/style.css' %}">
    <title>
      {% block title %}{% endblock %}
    </title>
    <style>
        body {
          padding : 10px;
        }
        div.container {
          min-width:600px;
        }
        nav ul#menu {
            margin:0px;
            padding:0px;
        }
        nav ul#menu li {
            float:right;
            list-style-type:none;
            text-decoration: underline;
            padding-left: 10px;
        }
        div.header {
          padding: 10px;
        }
        div.header * {
          color: white !important;
        }
        div.header .title {
          font-size: 1.2em;
        }
        {% block styles %}{% endblock %}
    </style>
</head>
<body>
<div class="container">
  <div class="inner">
    <div class="row header mysystem-header">
      <div class="col-sm title">
        {% block headertitle %}{% endblock %}
      </div>
      <div class="col-sm">
        <nav class="pull-right">
            <ul id="menu">
              {% if user.is_authenticated %}
                <li><a href="{% url 'logout' %}" class="logout">Logout</a></li>
              {% else %}
              {% endif %}
            </ul>
        </nav>
      </div>
    </div>
    <br/>
    <div class="content">
        {% block content %}{% endblock %}
    </div>
  </div>
</div>
<script src="{% static "js/jquery-3.3.1.min.js" %}"></script>
<script src="{% static "js/bootstrap.bundle.min.js" %}"></script>
{% block scripts %}{% endblock %}
</body>
</html>

(7)registration\login.html

ログイン画面です
:point_up_2:templateファイルは、Djangoの「registration」フォルダに入れておく必要があります

registration\login.html
{% load staticfiles %}
{% load bootstrap4 %}
<!DOCTYPE html>
<html lang="ja-JP" >
<head>
    <title>ログイン</title>
    <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0">
    <link rel="stylesheet" type="text/css" href="/static/admin/css/responsive.css" />
    <link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
    <link rel="stylesheet" href="{% static 'css/style.css' %}">
    <style>
    .login #container {
        width: 30em !important;
        min-width: 300px;
        margin: 100px auto;
        border-style: solid;
        border-color: #3333FF;
    }
    div.form-group {
      padding-top: 20px;
    }
    div#header {
      padding: 10px;
    }
    div#content-main {
      padding: 20px;
    }
    </style>
</head>

<body class=" login" data-admin-utc-offset="32400">
<div id="container">
  <div class="inner">
    <div id="header" class="mysystem-header">
              ○○管理システム
    </div>
    <div id="content" class="colM">
      <div id="content-main">
        <form method="post" action="{% url 'login' %}">
        {% csrf_token %}
        {% bootstrap_form form layout='horizontal' %}
        <div class="form-group row px-3">
          <input type="submit" class="btn btn-primary btn-block" value="ログイン" />
        </div>
        <input type="hidden" name="next" value="{{ next }}" />
        </form>
      </div>  <!-- content-main -->
      <br class="clear" />
    </div>  <!-- content -->
  </div>  <!-- inner -->
</div>  <!-- END Container -->
</body>
</html>

:point_up_2:bootstrap_form でformを描画しています。
{% bootstrap_form form layout='horizontal' %}

(8)settings.py

settings.pyを編集します

settings.py

INSTALLED_APPS = [ 'accounts.apps.AccountsConfig', #追加 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'bootstrap4', #追加 ] ・・・・ # Authentication LOGIN_URL = '/' #ここで定義したURLに入ると、template/registration/login.htmlに移動 LOGIN_REDIRECT_URL = '/dashboard/' #任意の行に追加 LOGOUT_REDIRECT_URL = '/accounts/login/' #任意の行に追加 LOGOUT_REDIRECT_URL='/' #任意の行に追加

bootstrap4は、pipでインストールできます
ログアウトでは、ルートを表示するようにしておきます。

(9)myproject\urls.py

・accountsにアクセスすると、Djagoの認証用のサイトにアクセスします
・ルートにアクセスすると、「accounts」アプリのサイトにアクセスします

myproject\urls.py
・・・
urlpatterns = [
    path('accounts/', include('django.contrib.auth.urls')), #  追加
    path('', include('accounts.urls')), #"追加"
    path('admin/', admin.site.urls),
]

ルートのURLをaccountsにすることで、topページが表示されるようにしておきます。

動作確認してみます。

cmd.prompt
(venv) C:\data\python\work\myproject>manage.py runserver

http://localhost:8000/にアクセスします

image.png

「2.管理ユーザを作成する」で作成したスーパーユーザでログインしてみます

image.png

ログインできました。Djangoは、認証の実装も簡単ですね。


https://narito.ninja/blog/detail/86/



概要

Djangoフレームワークは多言語に対応するための機能も提供しており、割と簡単に扱うことができます。基本的な使い方と流れを説明していきます。


結論から言ってDjangoのi18nがカスタマイズi18nよりいいこと

1.Djangoに最適化されているので実装が速い
2.★Session管理を勝ってにしてくれる。ページが変わってもクライアントで選択した言語を維持してくれる

多言語対応の準備

多言語対応する上での準備です。

settings.py

まず、LANGUAGE_CODEjaとしておきます。これはおなじみの設定ですね。

LANGUAGE_CODE = 'ja'

特に多言語対応をしない場合ならば、このLANGUAGE_CODEに指定した言語で翻訳されていきます。しかし多言語対応、サイト上で複数の言語を扱う場合は追加の設定が必要です。
→LANGUAGE_CODEはDefault言語になる

MIDDLEWAREに、django.middleware.locale.LocaleMiddlewareを読み込ませる必要があります。

    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware',  # これ
    'django.middleware.common.CommonMiddleware',

これにより、Djangoはこのサイト上で複数の言語を扱うのだなと判断します。アクセスしてきたURLやセッション、使っているブラウザの言語といった情報をもとに、サイト上の文章を翻訳しようとします。

どうしてもユーザーの言語がわからない場合にだけ、LANGUAGE_CODEが使われるようになります。言い換えると、Localeミドルウェアを使わない場合は必ずLANGUAGE_CODEの言語で翻訳されるということになります。

ミドルウェアの場所にも注意です。SessionMidlewareCommonMiddlewareの間に書きます。キャッシュ関連ミドルウェアを使う場合は、CommonMiddlewareの前にLocaleMiddlewareが来るようにしてください(つまりキャッシュの後に読み込ませる)。

また、次の記述も追加しておきましょう。

LOCALE_PATHS = (
    os.path.join(BASE_DIR, 'locale'),
)

localeディレクトリの作成

プロジェクト直下と、アプリケーションディレクトリ内にlocaleというディレクトリを作成しておきましょう。

今回はappというアプリケーションです。次の画像のようになります。 2つのlocaleディレクトリがあることを確認しましょう。プロジェクト直下とアプリケーション直下にlocaleディレクトリを置く

Windowsの場合

翻訳ファイルを作成するためのツールが必要です。こちらにアクセスし、ダウンロードしてください。

多言語化したい文章を指定する

それでは、多言語化したい文章をマークしていきましょう。

テンプレート内の文章

テンプレート内で文章を多言語化する簡単な方法は、テンプレートタグtransを使う方法です。

{% load i18n %}
<h1>{% trans "Hello" %}</h1>

{% load i18n %}とするのを忘れないようにしましょう。{% trans %}をはじめとする、翻訳関連のテンプレートタグ・フィルタを読み込むための記述です。これがどういう動作をするかは、後々わかるようになります。

Pythonファイル内の文章

フォームのlabelやモデルのverbose_nameなど、そういった部分も多言語化できますし、テンプレートへ渡す文字列を多言語化したいかもしれません。

例えば、モデルの例です。

from django.db import models
from django.utils.translation import gettext_lazy as _


class Post(models.Model):
    title = models.CharField(_('post title'), max_length=255)

テンプレートではtransを使いましたが、Pythonファイル内ではdjango.utils.translation.gettext_lazy又はgettextをよく使います。lazyとついているのは遅延評価用...reverse関数とreverse_lazy関数の関係と似たようなものです。上の例ならば、titleフィールドの表示名を実際に表示する場面で翻訳をしてくれます。

モデルやフォームのフィールド内にあるlabel, verbose_name,help_text...モジュールのimport時にすぐに読み込まれるような場所、クラス属性内に関してはlazyを使うほうが無難です。

as _ として、gettext_lazy関数をアンダースコアで使えるようにしていますがこれは慣習的なものです。

翻訳ファイルの作成

以下のコマンドを実行しましょう。これは日本語の翻訳ファイルを作成するコマンドです。

django-admin makemessages -l ja

localeディレクトリの中に、jaというディレクトリができたはずです。おそらく、2通りの状態になります。

  1. アプリケーション直下のlocaleディレクトリだけ中身がある

  2. アプリケーション直下と、プロジェクト直下のlocaleディレクトリ両方に中身がある

Djangoが翻訳ファイルをどこに配置するかというと、まずアプリケーション内のlocaleディレクトリに配置しようとしますapp/models.pyに翻訳テキストがありましたが、これはappアプリケーション内のlocaleディレクトリに翻訳ファイルが置かれます。

なので、appアプリケーション内のlocaleディレクトリ内、django.poには次の記述が必ずあるはずです。

#: .\app\models.py:6
msgid "post title"
msgstr ""

特定のアプリケーションに属さないファイルはsettings.pyに定義したLOCALE_PATHSに翻訳ファイルが置かれます。特定のアプリケーションに属さないファイルというのは例えば、settings.pyやプロジェクト直下に置いたtemplates内の翻訳テキストが該当します。

なので、プロジェクト直下にtemplatesを置いている方であれば、プロジェクト直下のlocale内のdjango.poに次の記述があるはずです。

#: .\templates\app\top.html:7
msgid "Hello"
msgstr ""

templatesをアプリケーション内に置いている方ならば、プロジェクト直下のlocaleは空で、アプリケーション内のlocaleにそれがあります。

#: .\app\templates\app\top.html:7
msgid "Hello"
msgstr ""

アプリケーション内にlocaleディレクトリがない場合は、LOCALE_PATHSに翻訳ファイルが置かれます。なので、プロジェクト全ての翻訳を一か所にまとめたい場合は、アプリケーション内にlocaleディレクトリを置かないほうが良いかもしれません。また、LOCALE_PATHSも空の場合はエラーになります。

翻訳する

django.poというファイルがあるので開きましょう。

#: .\app\models.py:6
msgid "post title"
msgstr ""

#: .\app\templates\app\top.html:5
msgid "Hello"
msgstr ""

msgidは、{% trans Hello %}のHello部分であったり、_('post title')のpost title部分です。ここに入れたい日本語の文章をmsgstrに書いていきます。

#: .\app\models.py:6
msgid "post title"
msgstr "記事タイトル"

#: .\app\templates\app\top.html:5
msgid "Hello"
msgstr "こんにちは"

翻訳する文章を作成したら、コンパイルします。

django-admin compilemessages

これにより、django.moというファイルができます。これでひとまず終了です。

今後は、新しい言語を追加したり翻訳したい文章が増えればmakemessagesを行い、修正があればdjango.poを編集し、そしてcompilemessagesを行っていきます。

UnicodeEncodeErrorが出る場合

ロケールの問題が殆どです。CentOS7ならば、以下のようにロケールを変更できます。

localectl set-locale LANG=ja_JP.utf-8
source /etc/locale.conf

翻訳の上書き

翻訳ファイルの探索順序は、次のようになっています。

  1. settings.pyのLOCALE_PATHSが指している場所

    この記事で言うところの、プロジェクト直下のlocale

  2. 各アプリケーション内にあるlocaleディレクトリ(INSTALLED_APPSの上から順に)

    authやadminなどの組み込みのDjangoアプリケーション内にもlocaleがあります。

  3. django/conf/locale

    Django全体で使われる基本翻訳。例えば曜日の翻訳文章などはここで。

もしかしたら、Django標準の翻訳テキストを上書きしたい場合もあるかもしれませんし、サードパーティ製ライブラリの翻訳を上書きしたい、といったケースもあるかもしれません。

Django管理サイトのタイトルは"Django administration"なのですが、これを上書きするならばLOCALE_PATHS内で次のように定義を上書きすればOKです。

msgid "Django administration"
msgstr "管理サイトへようこそ!"

もしくは、アプリケーション内のlocaleで上書きすることも可能です。各アプリケーション内のlocaleディレクトリですが、INSTALLED_APPSの上から順に探索され、見つかったら探索が終わります。各種テンプレートやstaticファイルの探索順序と同じです。INSTALLED_APPSで自作アプリが上書きしたいアプリケーション(管理サイトタイトルならば、admin)より上にある状態にしておくことが必要です。

言語切り替えプルダウン

各ユーザーが、表示したい言語をそれぞれ切り替えれるようにしてみます。

urls.pyに以下を追加し...

path('i18n/', include('django.conf.urls.i18n')),

テンプレート内に以下の記述をしておきます。

{% load i18n %}

<form action="{% url 'set_language' %}" method="post">{% csrf_token %}
    <input name="next" type="hidden" value="{{ redirect_to }}">
    <select name="language">
        {% get_current_language as LANGUAGE_CODE %}
        {% get_available_languages as LANGUAGES %}
        {% get_language_info_list for LANGUAGES as languages %}
        {% for language in languages %}
            <option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected{% endif %}>
                {{ language.name_local }} ({{ language.code }})
            </option>
        {% endfor %}
    </select>
    <input type="submit" value="Go">
</form>

ヘッダーとかに置いておくと良さそうですね。
言語切り替えプルダウン

プルダウンの言語が多すぎる場合は、絞ることもできます。settings.pyに追記しましょう。

from django.utils.translation import ugettext_lazy as _
LANGUAGES = [
    ('en', _('English')),
    ('ja', _('Japanese')),
]

URLで言語を判断する

プロジェクトのurls.pyにて、以下のように定義します。

from django.conf.urls.i18n import i18n_patterns
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),

]

urlpatterns += i18n_patterns(
    path('', include('app.urls')),
)

いつもならば、path('', include('app.urls')),という記述はpath('admin/', admin.site.urls),の下あたりにありますね。しかし、今回は別の場所...i18n_patterns内に移しました。これにより、appの各URLに関しては、URLの頭に/ja/hogehoge とか/en/hogehoge とかでアクセスできるようになります。

https://intellectual-curiosity.tokyo/2018/12/18/django%E3%81%A7%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95/


Djangoでファイルをアップロードする方法

Djangoでファイルをアップロードする方法をご紹介します。

条件

  • Django 2.1.4
  • Python 3.7.0

アップロード処理の実装

「アップロード画面」と「アップロード完了画面」を作成します。

  • アップロード画面:ファイルを選択してアップロード実行
  • アップロード完了画面:アップロードが完了したら表示

アップロード先ディレクトリ作成

アップロードしたファイルの保存先として以下のディレクトリ(uploads)を作成します。

  1. projectName
  2. ├projectName
  3. └monitor
  4. ├migrations
  5. ├templates
  6. └uploads ←ここにアップロードしたファイルが保存される
  7. ・・
  8. ・・

urls.py

  1. # urls.py抜粋
  2. from django.urls import path
  3. from . import views
  4. app_name = 'monitor'
  5. urlpatterns = [
  6. ・・・
  7. # ファイルアップロード用
  8. path('monitor/upload/', views.upload, name='upload'),
  9. path('monitor/upload_complete/', views.upload_complete, name='upload_complete'),
  10. ]

views.py

  1. # views.py抜粋
  2. import os
  3. from .forms import UploadFileForm
  4. UPLOAD_DIR = os.path.dirname(os.path.abspath(__file__)) + '/uploads/' # アップロードしたファイルを保存するディレクトリ
  5. # アップロードされたファイルのハンドル
  6. def handle_uploaded_file(f):
  7. path = os.path.join(UPLOAD_DIR, f.name)
  8. with open(path, 'wb+') as destination:
  9. for chunk in f.chunks():
  10. destination.write(chunk)
  11. # ファイルアップロード
  12. def upload(request):
  13. if request.method == 'POST':
  14. form = UploadFileForm(request.POST, request.FILES)
  15. if form.is_valid():
  16. handle_uploaded_file(request.FILES['file'])
  17. return redirect('monitor:upload_complete') # アップロード完了画面にリダイレクト
  18. else:
  19. form = UploadFileForm()
  20. return render(request, 'monitor/upload.html', {'form': form})
  21. # ファイルアップロード完了
  22. def upload_complete(request):
  23. return render(request, 'monitor/upload_complete.html')

forms.py

  1. # forms.py
  2. from django import forms
  3. class UploadFileForm(forms.Form):
  4. file = forms.FileField()

テンプレート

アップロード画面:upload.html、アップロード完了画面:upload_complete.htmlを作成します。
(monitor/templates/monitorの下に作成)

  1. <!-- upload.html -->
  2. {% extends 'base.html' %}
  3. {% block content %}
  4. <h1>アップロード サンプル</h1>
  5. <form method="POST" enctype="multipart/form-data">
  6. {% csrf_token %}
  7. {{ form }}
  8. <div class="form-group">
  9. <button type="submit">アップロード</button>
  10. </div>
  11. </form>
  12. {% endblock %}
  1. <!-- upload_complete.html -->
  2. {% extends 'base.html' %}
  3. {% block content %}
  4. <h1>アップロード完了</h1>
  5. <a href="{% url 'monitor:upload' %}"><button>戻る</button></a>
  6. {% endblock %}

実行結果

Djangoを起動して以下のURLにアクセスします。

http://127.0.0.1:8000/monitor/upload/

ファイル選択とアップロードボタンが表示されます。

ファイルを選択せずにアップロードボタンを押すと、警告メッセージが表示されます。

ファイルを選択すると、選択したファイル名が表示されます。

アップロードを実行すると、アップロード完了画面に遷移します。

指定のディレクトリに対象ファイルがアップロードされました。

ファイル種別のバリデーション

アップロードするファイル種別を限定する処理を実装します。
ここではcsvファイルのみを許可するようにします。

forms.py

フォームにバリデーションチェックのメソッドを追加します。
(csv以外の拡張子も受け入れたい場合、対象の拡張子をVALID_EXTENSIONSに追加します。)

  1. # forms.py
  2. from django import forms
  3. import os
  4. VALID_EXTENSIONS = ['.csv']
  5. class UploadFileForm(forms.Form):
  6. file = forms.FileField(
  7. label='アップロードファイル',
  8. )
  9. def clean_file(self):
  10. file = self.cleaned_data['file']
  11. extension = os.path.splitext(file.name)[1] # 拡張子を取得
  12. if not extension.lower() in VALID_EXTENSIONS:
  13. raise forms.ValidationError('csvファイルを選択してください!')

実行結果

csv以外のファイルをアップロードしようとするとエラーメッセージが表示されます。

参考

サンプルソース

サンプルソースをGitHubに公開しています。
https://github.com/kzmrt/graph

Django公式:ファイルのアップロード

https://docs.djangoproject.com/ja/2.1/topics/http/file-uploads/


+ Recent posts