Django 進階(裝飾器,Mixin,訊號,模式)
阿新 • • 發佈:2019-02-13
Django中級用法
抽象models類
class BaseProfile(models.Model):
USER_TYPES = (
(0, 'Ordinary'),
(1, 'SuperHero'),
)
user = models.OnToOneField(settings.AUTH_USER_MODEL, primary_key=True)
user_type = models.IntegerField(max_length=1, null=True, choices=USER_TYPES)
bio = models.CharField(max_length=200 , blank=True, null=True)
def __str__(self):
return "{}:{:.20}".format(self.user, self.bio or "")
class Meta:
abstract = True
class SuperHeroProfile(models.Model):
origin = models.CharField(max_length=100, blank=True, null=True)
class Meta:
abstract = True
class OrdinaryProfile(models.Model):
address = models.CharField(max_length=200, blank=True, null=True)
class Meta:
abstract = True
class Profile(SuperHeroProfile, OrdinaryProfile, BaseProfile):
pass
擴充套件django內建User
定義(Profile.models)
# models.py
# 將primary_key賦值為True,以阻止類似PostgreSQL這樣的資料庫後端中的併發問題
class Profile(models.Model):
user = models.OnToOneField(settings.AUTH_USER_MODEL, primary_key=True)
訊號
# signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.conf import settings
from . import models
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_profile_handler(sender, instance, created, **kwargs):
if not created:
return
# 僅在created是最新時才建立賬戶物件
profile = models.Profile(user=instance)
profile.save()
首先,為你的應用建立一個__init__.py
包以引用應用的ProfileConfig
:
# __init__.py
default_app_config = "profile.apps.ProfileConfig"
接下來是app.py
中的子類ProfileConfig
方法,可使用ready方法配置訊號:
# apps.py
from django.apps import AppConfig
class ProfileConfig(AppConfig):
name = "profiles"
verbose_name = "User Profiles"
def ready(self):
from . import signals
為了操作方便,賬戶admin
可以通過定義一個自定義的UserAdmin
嵌入到預設的使用者admin
中:
# admin.py
from django.contrib import admin
from .models import Profile
from django.contrib.auth.models import User
class UserProfileInline(admin.StackedInline):
model = Profile
class UserAdmin(admin.UserAdmin):
inlines = [UserProfileInline]
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
更新:
上下文Mixin
class FeedMixin(object):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["feed"] = models.Post.objects.viewable_posts(self.request.user)
return context
注意雙星號包含的變數
class MyFeed(**FeedMixin**, generic.CreateView):
model = models.Post
template_name = "myfeed.html"
success_url = reverse_lazy("my_feed")
服務模式
# service.py
API_URL = "http://api.herocheck.com/?q={0}"
class SuperHeroWebAPI:
...
@staticmehtod
def is_hero(username):
url = API_URL.format(username)
return webclient.get(url)
加上黑名單功能的服務
class SuperHeroWebAPI:
...
@staticmethod
def is_hero(username):
blacklist = set(["syndrome", "kcka$$", "superfake"])
ulr = API_URL.format(username)
return username not in blacklist and webclient.get(url)
使用服務
from .services import SuperHeroWebAPI
def is_superhero(self):
return SuperHeroWebAPI.is_superhero(self.user.username)
模型的方法作為屬性
Python類可以使用property
裝飾器把函式當作一個屬性來使用。這樣,Django模型也可以較好地利用它。替換前面那個例子中的函式:
@property
def age(self):
...
現在我們可以用profile.age來訪問使用者的年齡。注意,函式的名稱要儘可能的短。
快取特性
很好理解,直接看程式碼:
from django.utils.function import cached_property
#...
@cached_property
def full_name(self):
# 代價高昂的操作,比如,外部服務呼叫
return "{0} {1}".format(self.firstname, self.lastname)
定製模型管理器(Manager)
太簡單了,直接看官網
Querysets 組合查詢
from django.db.models import Q
# Union 交集
>>> User.objects.filter(Q(username__in["a", "b", "c"]) | Q(username__in=["c", "d"]))
[`<User: a>, <User: b>, <User: c>, <User: d>`]
# Intersection 並集
>>> User.objects.filter(Q(username__in["a", "b", "c"]) & Q(username__in=["c", "d"]))
[<User: c>]
# Difference 補集
>>> User.objects.filter(Q(username__in=["a", "b", "c"]) & ~Q(username__in=["c", "d"]))
[<User: a>, <User: b>]
Querysets連結
這是一個很天真的做法,開銷很大:
>>> recent = list(posts)+list(comments)
>>> sorted(recent, key=lambda e: e.modified, reverse=True)[:3]
[<Post: user: Post1>, <Comment: user: Comment1>, <Post: user: Post0>]
一個更好的解決方案是使用迭代器減少記憶體消耗。如下,使用itertools.chain方法合併多個QuerySets:
>>> from itertools import chain
>>> recent = chain(posts, comments)
>>> sorted(recent, key=lambda e: e.modified, reverse=True)[:3]
以上:2016年05月31日14:51:32
裝飾器
第一種
@login_required
def simple_view(request):
return HttpResponse()
2 通過對基於函式檢視或者基於類檢視使用一個裝飾器實現控制:
@login_required(MyView.as_view())
3 通過覆蓋mixin的類檢視的dispatch
方法實現控制:
class LoginRequiredMixin:
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
nav active的問題
每個模板都要包含下列程式碼:
{% include "_navbar.html" with active_link='link2' %}
然後
{# _navbar.html #}
<ul class="nav nav-pills">
<li{% if active_link == "link1" %} class="active"{% endif %}><a
href="{% url 'link1' %}">Link 1</a></li>
<li{% if active_link == "link2" %} class="active"{% endif %}><a
href="{% url 'link2' %}">Link 2</a></li>
<li{% if active_link == "link3" %} class="active"{% endif %}><a
href="{% url 'link3' %}">Link 3</a></li>
</ul>
之前我都是從view傳一個current_page
變數來判斷的,好蠢
Template tag
# app/templatetags/nav.py
from django.core.urlresolvers import resolve
from django.template import Library
register = Library()
@register.simple_tag
def active_nav(request, url):
url_name = resolve(request.path).url_name
if url_name == url:
return "active"
return ""
使用:
{# base.html #}
{% load nav %}
<ul class="nav nav-pills">
<li class={% active_nav request 'active1' %}><a href="{% url
'active1' %}">Active 1</a></li>
<li class={% active_nav request 'active2' %}><a href="{% url
'active2' %}">Active 2</a></li>
<li class={% active_nav request 'active3' %}><a href="{% url
'active3' %}">Active 3</a></li>
</ul>
表單
動態表單
# forms.py
class PersonDetailsForm(forms.Form):
name = forms.CharField(max_length=100)
age = forms.IntegerField()
def __init__(self, *args, **kwargs):
upgrade = kwargs.pop("upgrade", False)
super().__init__(*args, **kwargs)
# Show first class option? 顯示頭等艙選項?
if upgrade:
self.fields["first_class"] = forms.BooleanField(label="Fly First Class?")
# views.py
class PersonDetailsEdit(generic.FormView):
...
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs["upgrade"] = True
return kwargs
使用者表單(表單需要根據已經登入的使用者來進行定製)
需要django-braces
庫
from braces.forms import UserKwargModelFormMixin
class PersonDetailsForm(UserKwargModelFormMixin, forms.Form):
...
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Are you a member of the VIP group?
if self.user.groups.filter(name="VIP").exists():
self.fields["first_class"] = forms.BooleanField(label="Fly First Class?")
然後再確認只有登入使用者才能看到此檢視
class VIPCheckFormView(LoginRequiredMixin, UserFormKwargsMixin,
generic.FormView):
form_class = PersonDetailsForm
...
一個檢視的多個表單行為
crispy表單訂閱器修改不同的按鈕
# forms.py
class SubscribeForm(forms.Form):
email = forms.EmailField()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.layout.append(Submit('subscribe_butn', 'Subscribe'))
UnSubscribeForm
以完全相同的方式來定義,除了按鈕不同
# views.py
from .forms import SubscribeForm, UnSubscribeForm
class NewsletterView(generic.TemplateView):
subcribe_form_class = SubscribeForm
unsubcribe_form_class = UnSubscribeForm
template_name = "newsletter.html"
def get(self, request, *args, **kwargs):
kwargs.setdefault("subscribe_form", self.subcribe_form_class())
kwargs.setdefault("unsubscribe_form", self.unsubcribe_form_class())
return super().get(request, *args, **kwargs)
單獨來看post
方法
def post(self, request, *args, **kwargs):
form_args = {
'data': self.request.POST,
'files': self.request.FILES,
}
if "subscribe_butn" in request.POST:
form = self.subcribe_form_class(**form_args)
if not form.is_valid():
return self.get(request,
subscribe_form=form)
return redirect("success_form1")
elif "unsubscribe_butn" in request.POST:
form = self.unsubcribe_form_class(**form_args)
if not form.is_valid():
return self.get(request,
unsubscribe_form=form)
return redirect("success_form2")
return super().get(request)
這裡我感覺登入和註冊兩個表單可以用這個方法
可以偷懶的第三方包^_^
package | desc | 例 |
---|---|---|
django-braces | 通用檢視擴充套件(Mixin) | StaticContextMixin |
django-crispy-forms | form前端標籤擴充套件 | {% crispy_form %} |
zebra stripes | are neat | $1 |