Tuesday 30 April 2019
django 32 sending email reference
reference:
https://medium.com/@_christopher/how-to-send-emails-with-python-django-through-google-smtp-server-for-free-22ea6ea0fb8e
https://bootsnipp.com/snippets/ykXa
https://www.sanwebe.com/2013/03/addremove-input-fields-dynamically-with-jquery
https://code.djangoproject.com/ticket/1130
Monday 29 April 2019
Saturday 27 April 2019
加拿大美国生活总结
1. 吃喝嫖赌 - 列治文;
2. 吃福利 - 蒙特利尔;
3. 中产 - 多伦多;
4. 小康 - 卡在家里,挨的猛冻;
5. 大款 - 西温哥华;
6. 官宦 - 温哥华西;
7. 非主流 - 纽芬兰,萨省;
8. 学渣 - 温尼伯;
9. 佛系 - 维多利亚
1. 吃喝嫖赌 - 洛杉矶;
2. 学霸 - 硅谷;
3. 偷渡客,政治难民 - 法拉盛;
4. 中产 - 西雅图,波士顿;
5. 小康 - 休斯顿,新泽西;
6. 官宦 - 曼哈顿上东;
2. 吃福利 - 蒙特利尔;
3. 中产 - 多伦多;
4. 小康 - 卡在家里,挨的猛冻;
5. 大款 - 西温哥华;
6. 官宦 - 温哥华西;
7. 非主流 - 纽芬兰,萨省;
8. 学渣 - 温尼伯;
9. 佛系 - 维多利亚
1. 吃喝嫖赌 - 洛杉矶;
2. 学霸 - 硅谷;
3. 偷渡客,政治难民 - 法拉盛;
4. 中产 - 西雅图,波士顿;
5. 小康 - 休斯顿,新泽西;
6. 官宦 - 曼哈顿上东;
django 31 update user profile, password
logged in as bob, show bob's posts
click update profile, change username to bobby
profile updated, select bobby's posts
display all bobby's posts (originally bob's)
select change password, error if old password not match
all field entered right, password updated, bobby still logged in
log out bobby, and log in again with new password
login successful
#urls
from django.urls import path
from . import views
app_name = 'music'
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('register/', views.UserRegistration.as_view(), name='register'),
path('editProfile/', views.UserProfileUpdate.as_view(), name='profile-update'),
path('changePassword/', views.UserPasswordUpdate, name='password-update'),
path('login/', views.UserLogin.as_view(), name='login'),
path('logout/', views.UserLogout, name='logout'),
#/music/id/
path('<pk>/', views.DetailView.as_view(), name='detail'),
#music/album/add/
path('album/add/', views.AlbumCreate.as_view(), name='album-add'),
#music/album/id/
path('album/<pk>/', views.AlbumUpdate.as_view(), name='album-update'),
#music/album/id/delete/
path('album/<pk>/delete/', views.AlbumDelete.as_view(), name='album-delete'),
#music/song/add/
path('song/<album_pk>/add/', views.SongCreate.as_view(), name='song-add'),
#music/song/id/
path('song/<album_pk>/<pk>/', views.SongUpdate.as_view(), name='song-update'),
#music/song/id/delete/
path('song/<album_pk>/<pk>/delete/', views.SongDelete.as_view(), name='song-delete'),
]
-------------------------------------------------------------
#views
from django.views import generic
from .models import Album, Song
from django.views.generic.edit import CreateView, UpdateView, DeleteView, View
from django.urls import reverse_lazy
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, logout, update_session_auth_hash
from .forms import RegistrationForm, LoginForm, ProfileUpdateForm
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.contrib import messages
from django.contrib.auth.models import User
from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth.decorators import login_required
post_num = 4
author_name = None
#get list of username
def get_authors():
authorNames = []
for author in User.objects.all():
authorNames.append(author.username)
return authorNames
author_names = get_authors()
class IndexView(generic.ListView):
template_name = 'music/index.html'
paginate_by = post_num
def get_paginate_by(self, queryset):
global post_num
post_num = self.request.GET.get('postNum', self.paginate_by)
return post_num
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
context['post_num'] = post_num
context['author_name'] = author_name
context['all_authors_names'] = author_names
return context
def get_queryset(self):
global author_name
author_name = self.request.GET.get('userName', None)
if(author_name not in [None,'None','All']):
author_id = User.objects.get(username=author_name).pk
return Album.objects.filter(author=author_id).order_by('-date_posted')
else:
return Album.objects.all()
class DetailView(generic.DetailView):
model = Album
template_name = 'music/detail.html'
#protected route, require authentication
class AlbumCreate(LoginRequiredMixin, CreateView):
model = Album
fields = ['artist', 'album_title', 'genre', 'album_logo']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
class AlbumUpdate(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Album
fields = ['artist', 'album_title', 'genre', 'album_logo']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def test_func(self):
album = self.get_object()
if self.request.user == album.author:
return True
return False
class AlbumDelete(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Album
#redirect to home page after delete
success_url = reverse_lazy('music:index')
def test_func(self):
album = self.get_object()
if self.request.user == album.author:
return True
return False
#protected route, require authentication
class SongCreate(LoginRequiredMixin, CreateView):
model = Song
fields = ['file_type', 'song_title', 'is_favorite']
#overwrite field['album']
def form_valid(self, form):
form.instance.album = Album.objects.get(pk=self.kwargs['album_pk'])
return super().form_valid(form)
#pass variable song_album to song_form template
def get_context_data(self, **kwargs):
context = super(SongCreate, self).get_context_data(**kwargs)
song_album = Album.objects.get(pk=self.kwargs['album_pk']).album_title
context.update({'song_album':song_album})
return context
class SongUpdate(LoginRequiredMixin, UpdateView):
model = Song
fields = ['file_type', 'song_title', 'is_favorite']
# overwrite field['album']
def form_valid(self, form):
form.instance.album = Album.objects.get(pk=self.kwargs['album_pk'])
return super().form_valid(form)
# pass variable song_album to song_form template
def get_context_data(self, **kwargs):
context = super(SongUpdate, self).get_context_data(**kwargs)
song_album = Album.objects.get(pk=self.kwargs['album_pk']).album_title
context.update({'song_album': song_album})
return context
class SongDelete(LoginRequiredMixin, DeleteView):
model = Song
#redirect to detail page after delete
def get_success_url(self):
return reverse_lazy('music:detail',kwargs={'pk': self.kwargs['album_pk']})
#user registration
class UserRegistration(View):
form_class = RegistrationForm
template_name = 'music/registration_form.html'
def get(self, request):
#get request, display UserForm with empty fields
form = self.form_class(None)
return render(request, self.template_name, {'form': form})
def post(self, request):
form = self.form_class(request.POST)
if form.is_valid():
user = form.save(commit=False)
#clearned/normalized data
password = form.cleaned_data['password']
user.set_password(password)
user.save()
messages.success(request, 'registration successful')
return redirect('music:login')
return render(request, self.template_name, {'form': form})
#user profile update
class UserProfileUpdate(LoginRequiredMixin, View):
form_class = ProfileUpdateForm
template_name = 'music/registration_form.html'
def get(self, request):
#get request, display UserForm with prefilled fields
form = ProfileUpdateForm(instance=self.request.user)
return render(request, self.template_name, {'form': form})
def post(self, request):
form = ProfileUpdateForm(request.POST, instance=self.request.user)
if form.is_valid():
form.save()
#update author_names
global author_names
author_names = get_authors()
messages.success(request, 'profile updated')
return redirect('music:index')
return render(request, self.template_name, {'form': form})
#user password update
@login_required
def UserPasswordUpdate(request):
if request.method == 'POST':
form = PasswordChangeForm(request.user, request.POST)
if form.is_valid():
user = form.save()
#keep current user logged in
update_session_auth_hash(request, user)
messages.success(request, 'password updated!')
return redirect('music:index')
else:
messages.error(request, 'Please correct the error below.')
else:
form = PasswordChangeForm(request.user)
return render(request, 'music/registration_form.html', {'form': form})
#user login
class UserLogin(View):
form_class = LoginForm
template_name = 'music/login_form.html'
def get(self, request):
#get request, display UserForm with empty fields
form = self.form_class(None)
return render(request, self.template_name, {'form': form})
def post(self, request):
form = self.form_class(request.POST)
if form.is_valid():
#clearned/normalized data
username = form.cleaned_data['username']
password = form.cleaned_data['password']
#returns User objects if credentials are correct
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
return redirect('music:index')
messages.error(request, 'error logging in')
return render(request, self.template_name, {'form': form})
def UserLogout(request):
logout(request)
return redirect('music:index')
--------------------------------------------
#forms
from django.contrib.auth.models import User
from django import forms
from django.contrib.auth.forms import UserChangeForm
class RegistrationForm(forms.ModelForm):
password = forms.CharField(widget=forms.PasswordInput)
class Meta:
model = User
fields = ['username', 'email', 'password']
class LoginForm(forms.Form):
username = forms.CharField(widget=forms.TextInput())
password = forms.CharField(widget=forms.PasswordInput)
fields = ['username', 'password']
class ProfileUpdateForm(UserChangeForm):
def __init__(self, *args, **kwargs):
super(ProfileUpdateForm, self).__init__(*args, **kwargs)
del self.fields['password']
class Meta:
model = User
fields = ('username','email','first_name','last_name')
--------------------------------------------
#templates/base
<!DOCTYPE html>
{% load static %}
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- bootstrap -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/css/bootstrap.min.css">
<!-- google fonts -->
<link href="https://fonts.googleapis.com/css?family=Srisakdi" rel="stylesheet">
<!-- my css -->
<link rel="stylesheet" type="text/css" href="{% static 'music/style.css' %}">
<title>{% block title %}Django{% endblock %}</title>
</head>
<body>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
<nav class="navbar navbar-expand-lg navbar-light" style="background-color: #fcefe5;">
<a class="navbar-brand" style="font-family: 'Srisakdi', serif; font-weight: bold; text-shadow: 4px 4px 4px #aaa;" href="{% url 'music:index' %}">Django</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarTogglerDemo02" aria-controls="navbarTogglerDemo02" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarTogglerDemo02">
<ul class="navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="{% url 'music:index' %}"><span class="glyphicon glyphicon-cd" aria-hidden="true"></span>  Albums</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#"><span class="glyphicon glyphicon-music" aria-hidden="true"></span>  Songs</a>
</li>
</ul>
<form class="navbar-form navbar-left" role="search" method="get" action="#">
<div class="form-group">
<input type="text" name="q" value=""/>
</div>
<button type="submit" >Search</button>
</form>
<ul class="navbar-nav navbar-right">
<li>
<a class="nav-link" href="{% url 'music:album-add' %}">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add Album
</a>
</li>
<li>
{% if user.is_authenticated %}
<a class="nav-link" href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="glyphicon glyphicon-user" aria-hidden="true"></span> {{user.username}} ▼
</a>
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
<a class="dropdown-item" href="{% url 'music:logout' %}">
<span class="glyphicon glyphicon-off" aria-hidden="true"></span> Log out
</a>
<a class="dropdown-item" href="{% url 'music:profile-update' %}">
<span class="glyphicon glyphicon-edit" aria-hidden="true"></span> Update Profile
</a>
<a class="dropdown-item" href="{% url 'music:password-update' %}">
<span class="glyphicon glyphicon-barcode" aria-hidden="true"></span> Change Password
</a>
</div>
{% else %}
<a class="nav-link" href="{% url 'music:login' %}">
<span class="glyphicon glyphicon-user" aria-hidden="true"></span> Login
</a>
{% endif %}
</li>
</ul>
</div>
</nav>
{% if messages %}
{% for message in messages %}
{% if message.tags == 'error' %}
<div class="alert alert-danger">{{message}}</div>
{% else %}
<div class="alert alert-{{message.tags}}">{{message}}</div>
{% endif %}
{% endfor %}
{% endif %}
{% block body %}
{% endblock %}
</body>
</html>
reference:
http://chuanshuoge2.blogspot.com/2019/04/django-31-update-user-profile.html
Friday 26 April 2019
29条人生经验
- 真正坚持到最后的人靠的不是激情,而是恰到好处的喜欢和投入。
- 一个人明白自己能做什么远比自己想做什么重要得多,前者需要给自己设定一个现实的疆域,是一种尽力而为的勤奋,后者是任性催生的热情,往往来自于对世界的无知和自负。一个人,一定要清楚地认识自己。
- 便宜莫贪,天下没有免费的午餐。想得到任何东西之前都要先问问自己,是否付得起相应的代价。
- 在早上把你叫醒的,不应该是闹钟,而应该是昨天早睡。
- 太用力的人,跑不远,别把人生的马拉松当成百米冲刺;太用力的爱不仅让自己累,也让身边的人累,最终难以圆满。
- “事非宜,勿轻诺。苟轻诺,进退错。”没有把握的事情不要轻易答应,如果答应了做不好,进退都是错误的,很容易让自己陷入里外不是人的尴尬局面。
- 你不必去找人脉,唯一需要操心的是要把自己的本事练好。
- 一定要去掌控自己的生活,而不要让生活带着你走。
- 婚姻里的指责和抱怨不是因为“我爱你”,而是因为“我情绪管理不好”。指责不是爱,而是婚姻的杀手。
- 成熟不等于世故,知世故而不世故,才是善良的成熟。
- 你其实并没有想象中那么依赖父母,但父母依赖你的程度远远超过于你的想象。
- 分开的时候一定要用力告别,因为说再见,也许真的是再也不见。
- 所有你此刻觉得不可承受,不可跨越的苦难,一旦经受住了,回头看,不过是浮云一片。
- 当你的学业、工作、生活不顺利的时候,切记不要把爱情当成你的救命稻草。
- 我们总是喜欢拿顺其自然来敷衍人生道路上的荆棘坎坷,却很少承认,真正的顺其自然,其实是竭尽所能之后的不强求,而非两手一摊的不作为。
- 耳不闻人之非,目不视人之短,口不言人之过。
- 看破不说破,知人不评人,知理不争论。刻薄嘴欠和幽默是两回事,口无遮拦和坦率是两回事,没有教养和随性是两回事。
- 在事情没有成功之前,不要在人前谈及任何有关的计划和想法。世界不会在意你的自尊,只是你的成就。
- 有事情是要说出来的,不要等着对方去领悟,因为对方不是你,不知道你想要什么,等到最后只能是伤心和失望,尤其是感情。
- 社会真的很残酷,你的能力和价值越低,被淘汰的就越快。
- 最简单却也是最难的事:少熬夜,多看书,多喝热水,多运动,用心爱一个人。
- 无论是男人还是女人,都要学着去做家务。做家务不是谁伺候“谁”的问题,而是培养一个人独立生活的技能。
- 照顾好自己的身体,很有必要。一个很差的身体带给你的局限不可能靠意志力突破。
- 社会没有那么复杂,复杂的是人心。
- 不管外面的世界究竟是什么样,能影响到你的,其实就身边的几个人。没有能力改变这世界,至少可以选择与谁同行。
- 遇到那种始终不在一个频道死活聊不到一个点上的人就别强求了。在婚姻里,精神上的门当户对比物质更重要。
- 奋斗就是每一天很难,可一年一年却越来越容易;不奋斗就是每天都很容易,可一天一天越来越难。
- 无论是工作,生活,还是婚姻,不要去攀比嫉妒,没有对比就没有伤害。一生很长,疲惫、烦躁、失望都会有,看清真相后,请依然热爱生活。
- 最后一个道理其实是悖论,就是无论你听过再多的道理,当你没有经历过一些事情或者心智没有达到这个境界的时候,你是不会理解这个道理的。或者,你以为你懂了,其实,你还不懂,你还是要一步一个跟头地去修行。
Thursday 25 April 2019
django 31 update user registration reference
reference:
https://docs.djangoproject.com/en/2.2/topics/auth/default/
https://simpleisbetterthancomplex.com/tutorial/2016/07/22/how-to-extend-django-user-model.html
https://stackoverflow.com/questions/11494483/django-class-based-view-how-do-i-pass-additional-parameters-to-the-as-view-meth
https://docs.djangoproject.com/en/2.2/topics/class-based-views/intro/
https://stackoverflow.com/questions/20171662/django-user-account-update-ignore-user-with-this-username-already-exists
https://simpleisbetterthancomplex.com/tips/2016/08/04/django-tip-9-password-change-form.html
Wednesday 24 April 2019
Tuesday 23 April 2019
城市生活指数澳洲vs.国内
Lance Shi 在知乎上回答关于是否该移民澳洲的问题时称,专业方向较好的程序员在澳洲几年下来 10 万澳元是保底,15 万澳元稍好,有的人 18 万澳元也没问题。
这个说法得到了其他人的验证,有网友举了个身边的例子:一个国内工作5年的码农,北京开的 20 万的工资,澳洲公司给 9 万刀 base,相当于 45 万人民币,super(养老金)另算,而且不用加班,看项目分红。
但澳洲,特别是悉尼的生活成本比国内要高得多。城市生活指数对比平台 Expatistan 对北京和悉尼在衣食住行、医疗和娱乐方面做了一个对比,结果显示,在北京生活总体要比在悉尼生活便宜 37%。
物价成本缩短了汇率差距。Lance Shi 认为,虽然澳币现在的汇率约是 1:4.8,但是如果按照购买力算的话,换算成 1:2 到 1:3 之间才比较恰当。
“换句话说,如果是 15 万澳币的年薪的话,应该对应成国内差不多 35-40 万年薪的生活水平,而不是 75 万。”所以对于过惯了国内生活的新移民来说,即便按汇率换算薪水比国内高,生活质量也有可能会严重下降。
根据澳洲统计局去年5月份的薪资数据显示,在未扣除税款和红利的情况下,澳人的平均周薪约为 1500 澳元,这相当于全职员工的平均年收入约为 8.2 万澳元。
这个说法得到了其他人的验证,有网友举了个身边的例子:一个国内工作5年的码农,北京开的 20 万的工资,澳洲公司给 9 万刀 base,相当于 45 万人民币,super(养老金)另算,而且不用加班,看项目分红。
但澳洲,特别是悉尼的生活成本比国内要高得多。城市生活指数对比平台 Expatistan 对北京和悉尼在衣食住行、医疗和娱乐方面做了一个对比,结果显示,在北京生活总体要比在悉尼生活便宜 37%。
物价成本缩短了汇率差距。Lance Shi 认为,虽然澳币现在的汇率约是 1:4.8,但是如果按照购买力算的话,换算成 1:2 到 1:3 之间才比较恰当。
“换句话说,如果是 15 万澳币的年薪的话,应该对应成国内差不多 35-40 万年薪的生活水平,而不是 75 万。”所以对于过惯了国内生活的新移民来说,即便按汇率换算薪水比国内高,生活质量也有可能会严重下降。
根据澳洲统计局去年5月份的薪资数据显示,在未扣除税款和红利的情况下,澳人的平均周薪约为 1500 澳元,这相当于全职员工的平均年收入约为 8.2 万澳元。
Sunday 21 April 2019
django 30 filter by query
default 4 post/page, author all
change author to bob, only show bob's posts
change to 6 posts/page, still only show bob's posts
url http://127.0.0.1:8000/music/?postNum=6&userName=bob
select 2nd page, still show 6 posts/page, only bob's posts
url http://127.0.0.1:8000/music/?postNum=6&userName=bob&page=2
#views
from django.views import generic
from .models import Album, Song
from django.views.generic.edit import CreateView, UpdateView, DeleteView, View
from django.urls import reverse_lazy
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, logout
from .forms import RegistrationForm, LoginForm
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.contrib import messages
from django.contrib.auth.models import User
post_num = 4
author_name = None
#get list of username
author_names = []
for author in User.objects.all():
author_names.append(author.username)
class IndexView(generic.ListView):
template_name = 'music/index.html'
paginate_by = post_num
def get_paginate_by(self, queryset):
global post_num
post_num = self.request.GET.get('postNum', self.paginate_by)
return post_num
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
context['post_num'] = post_num
context['author_name'] = author_name
context['all_authors_names'] = author_names
return context
def get_queryset(self):
global author_name
author_name = self.request.GET.get('userName', None)
if(author_name not in [None,'None','All']):
author_id = User.objects.get(username=author_name).pk
return Album.objects.filter(author=author_id).order_by('-date_posted')
else:
return Album.objects.all()
---------------------------------------------
#templates/index
{% extends 'music/base.html' %}
{% block title %}Albums{% endblock %}
{% block body %}
<div class="container-fluid">
<div class="row justify-content-end align-items-center pr-4">
<form action="" method="get" >
posts
<select name="postNum" onchange="this.form.submit()">
<option>{{post_num}}</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>6</option>
<option>8</option>
<option>12</option>
<option>16</option>
</select>
<input type="hidden" name="userName" value={{author_name}}>
</form>
   
<form action="" method="get" >
author
<select name="userName" onchange="this.form.submit()">
<option>{{author_name}}</option>
<option>All</option>
{% for author in all_authors_names %}
<option>{{author}}</option>
{% endfor %}
</select>
<input type="hidden" name="postNum" value={{post_num}}>
</form>
</div>
<div class="row mt-4">
{% for album in object_list %}
<div class="col" style="margin-left: auto; margin-right: auto; margin-top: 5px; margin-bottom: 5px;">
<div class="card" style="width: 200px;">
<img src="{{album.album_logo.url}}" class="card-img-top" style="height: 150px" alt="{{album.album_title}}">
<div class="card-body">
<div style="height: 40px"><h5 class="card-title">Album: {{album.album_title}}</h5></div>
<div style="height: 40px"><p class="card-text">Artist: {{album.artist}}</p></div>
<!-- Detail -->
<a href="{% url 'music:detail' album.id %}" class="btn btn-primary btn-sm">View Detail</a>
<!-- Edit -->
<a class="btn btn-default btn-sm" href="{% url 'music:album-update' album.id %}">
<span class="glyphicon glyphicon-pencil"></span>
</a>
<!-- Delete -->
<a class="btn btn-default btn-sm" href="{% url 'music:album-delete' album.id %}">
<span class="glyphicon glyphicon-trash"></span>
</a>
</div>
<div class="card-footer text-muted">
Created by {{album.author}} @ {{album.date_posted}}
</div>
</div>
</div>
{% endfor %}
</div>
<div class="row justify-content-between align-items-center mt-4">
<div>
{% if is_paginated %}
{% if page_obj.has_previous %}
<a class="btn btn-pagination" href="?postNum={{post_num}}&userName={{author_name}}&page=1" >First</a>
<a class="btn btn-pagination" href="?postNum={{post_num}}&userName={{author_name}}&page={{page_obj.previous_page_number}}">Previous</a>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<a class="btn btn-primary" href="?postNum={{post_num}}&userName={{author_name}}&page={{ num }}">{{ num }}</a>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<a class="btn btn-pagination" href="?postNum={{post_num}}&userName={{author_name}}&page={{ num }}">{{ num }}</a>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<a class="btn btn-pagination" href="?postNum={{post_num}}&userName={{author_name}}&page={{ page_obj.next_page_number }}">Next</a>
<a class="btn btn-pagination" href="?postNum={{post_num}}&userName={{author_name}}&page={{page_obj.paginator.num_pages}}">Last</a>
{% endif %}
{% endif %}
</div>
</div>
</div>
{% endblock %}
reference:
https://www.django-rest-framework.org/api-guide/filtering/
https://stackoverflow.com/questions/15044778/how-to-get-user-id-from-auth-user-table-in-django
Friday 19 April 2019
django 29 dynamic pagination
default 4 posts/page
change to 3 post/page
select page 3
url http://127.0.0.1:8000/music/?postNum=3&page=3
#views
from django.views import generic
from .models import Album, Song
from django.views.generic.edit import CreateView, UpdateView, DeleteView, View
from django.urls import reverse_lazy
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, logout
from .forms import RegistrationForm, LoginForm
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.contrib import messages
#default 4 post/page
post_num = 4
class IndexView(generic.ListView):
template_name = 'music/index.html'
paginate_by = post_num
def get_paginate_by(self, queryset):
global post_num
post_num = self.request.GET.get('postNum', self.paginate_by)
return post_num
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
context.update({'post_num':post_num})
return context
def get_queryset(self):
return Album.objects.all()
-------------------------------------------------------------
#template/index
{% extends 'music/base.html' %}
{% block title %}Albums{% endblock %}
{% block body %}
<div class="container-fluid">
<div class="row justify-content-end align-items-center pr-4">
<form action="" method="get" >
posts
<select name="postNum" onchange="this.form.submit()">
<option>{{post_num}}</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>6</option>
<option>8</option>
<option>12</option>
<option>16</option>
</select>
</form>
</div>
<div class="row mt-4">
{% for album in object_list %}
<div class="col" style="margin-left: auto; margin-right: auto; margin-top: 5px; margin-bottom: 5px;">
<div class="card" style="width: 200px;">
<img src="{{album.album_logo.url}}" class="card-img-top" style="height: 150px" alt="{{album.album_title}}">
<div class="card-body">
<div style="height: 40px"><h5 class="card-title">Album: {{album.album_title}}</h5></div>
<div style="height: 40px"><p class="card-text">Artist: {{album.artist}}</p></div>
<!-- Detail -->
<a href="{% url 'music:detail' album.id %}" class="btn btn-primary btn-sm">View Detail</a>
<!-- Edit -->
<a class="btn btn-default btn-sm" href="{% url 'music:album-update' album.id %}">
<span class="glyphicon glyphicon-pencil"></span>
</a>
<!-- Delete -->
<a class="btn btn-default btn-sm" href="{% url 'music:album-delete' album.id %}">
<span class="glyphicon glyphicon-trash"></span>
</a>
</div>
<div class="card-footer text-muted">
Created by {{album.author}} @ {{album.date_posted}}
</div>
</div>
</div>
{% endfor %}
</div>
<div class="row justify-content-between align-items-center mt-4">
<div>
{% if is_paginated %}
{% if page_obj.has_previous %}
<a class="btn btn-pagination" href="?postNum={{post_num}}&page=1" >First</a>
<a class="btn btn-pagination" href="?postNum={{post_num}}&page={{page_obj.previous_page_number}}">Previous</a>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<a class="btn btn-primary" href="?postNum={{post_num}}&page={{ num }}">{{ num }}</a>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<a class="btn btn-pagination" href="?postNum={{post_num}}&page={{ num }}">{{ num }}</a>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<a class="btn btn-pagination" href="?postNum={{post_num}}&page={{ page_obj.next_page_number }}">Next</a>
<a class="btn btn-pagination" href="?postNum={{post_num}}&page={{page_obj.paginator.num_pages}}">Last</a>
{% endif %}
{% endif %}
</div>
</div>
</div>
{% endblock %}
https://stackoverflow.com/questions/14716270/dynamic-pagination-using-generic-listview
https://stackoverflow.com/questions/5500472/how-do-i-match-the-question-mark-character-in-a-django-url
Thursday 18 April 2019
django 28 pagination
middle page
first page
last page
#views
class IndexView(generic.ListView):
template_name = 'music/index.html'
paginate_by = 4
def get_queryset(self):
return Album.objects.all()
-----------------------------------
#template/index
{% extends 'music/base.html' %}
{% block title %}Albums{% endblock %}
{% block body %}
<div class="container-fluid">
<div class="row">
{% for album in object_list %}
<div class="col" style="margin-left: auto; margin-right: auto; margin-top: 5px; margin-bottom: 5px;">
<div class="card" style="width: 200px;">
<img src="{{album.album_logo.url}}" class="card-img-top" style="height: 150px" alt="{{album.album_title}}">
<div class="card-body">
<div style="height: 40px"><h5 class="card-title">Album: {{album.album_title}}</h5></div>
<div style="height: 40px"><p class="card-text">Artist: {{album.artist}}</p></div>
<!-- Detail -->
<a href="{% url 'music:detail' album.id %}" class="btn btn-primary btn-sm">View Detail</a>
<!-- Edit -->
<a class="btn btn-default btn-sm" href="{% url 'music:album-update' album.id %}">
<span class="glyphicon glyphicon-pencil"></span>
</a>
<!-- Delete -->
<a class="btn btn-default btn-sm" href="{% url 'music:album-delete' album.id %}">
<span class="glyphicon glyphicon-trash"></span>
</a>
</div>
<div class="card-footer text-muted">
Created by {{album.author}} @ {{album.date_posted}}
</div>
</div>
</div>
{% endfor %}
</div>
#---------------pagination----------------
<div class="row justify-content-between align-items-center mt-4">
<div>
{% if is_paginated %}
{% if page_obj.has_previous %}
<a class="btn btn-pagination" href="?page=1" >First</a>
<a class="btn btn-pagination" href="?page={{page_obj.previous_page_number}}">Previous</a>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<a class="btn btn-primary" href="?page={{ num }}">{{ num }}</a>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<a class="btn btn-pagination" href="?page={{ num }}">{{ num }}</a>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<a class="btn btn-pagination" href="?page={{ page_obj.next_page_number }}">Next</a>
<a class="btn btn-pagination" href="?page={{page_obj.paginator.num_pages}}">Last</a>
{% endif %}
{% endif %}
</div>
</div>
#---------------pagination----------------
</div>
{% endblock %}
------------------------------------
#static/css
body{
height: 100vh;
background: white url("images/background.jpg");
background-repeat: repeat-y;
background-size: 100% 100%;
}
.btn-pagination{
background-color: black;
color: white;
}
.btn-pagination:hover{
color: yellow;
}
------------------------------
#template/base
<!DOCTYPE html>
{% load static %}
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- bootstrap -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/css/bootstrap.min.css">
<!-- google fonts -->
<link href="https://fonts.googleapis.com/css?family=Srisakdi" rel="stylesheet">
<!-- my css -->
<link rel="stylesheet" type="text/css" href="{% static 'music/style.css' %}">
<title>{% block title %}Django{% endblock %}</title>
</head>
<body>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
<nav class="navbar navbar-expand-lg navbar-light" style="background-color: #fcefe5;">
<a class="navbar-brand" style="font-family: 'Srisakdi', serif; font-weight: bold; text-shadow: 4px 4px 4px #aaa;" href="{% url 'music:index' %}">Django</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarTogglerDemo02" aria-controls="navbarTogglerDemo02" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarTogglerDemo02">
<ul class="navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="{% url 'music:index' %}"><span class="glyphicon glyphicon-cd" aria-hidden="true"></span>  Albums</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#"><span class="glyphicon glyphicon-music" aria-hidden="true"></span>  Songs</a>
</li>
</ul>
<form class="navbar-form navbar-left" role="search" method="get" action="#">
<div class="form-group">
<input type="text" name="q" value=""/>
</div>
<button type="submit" >Search</button>
</form>
<ul class="navbar-nav navbar-right">
<li>
<a class="nav-link" href="{% url 'music:album-add' %}">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add Album
</a>
</li>
<li>
{% if user.is_authenticated %}
<a class="nav-link" href="{% url 'music:logout' %}">
<span class="glyphicon glyphicon-off" aria-hidden="true"></span> Logout {{user.username}}
</a>
{% else %}
<a class="nav-link" href="{% url 'music:login' %}">
<span class="glyphicon glyphicon-user" aria-hidden="true"></span> Login
</a>
{% endif %}
</li>
</ul>
</div>
</nav>
{% if messages %}
{% for message in messages %}
{% if message.tags == 'error' %}
<div class="alert alert-danger">{{message}}</div>
{% else %}
<div class="alert alert-{{message.tags}}">{{message}}</div>
{% endif %}
{% endfor %}
{% endif %}
{% block body %}
{% endblock %}
</body>
</html>
--------------------------------------
#model
from django.db import models
from django.urls import reverse
from django.contrib.auth.models import User
class Album(models.Model):
artist = models.CharField(max_length=50)
album_title = models.CharField(max_length=50)
genre = models.CharField(max_length=50)
album_logo = models.FileField()
date_posted = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
#form submitted without action redirect to detail
def get_absolute_url(self):
return reverse('music:detail', kwargs={'pk': self.pk})
#query album.objects.get(pk=1)
def __str__(self):
return self.album_title + ' - ' + self.artist
class Song(models.Model):
album = models.ForeignKey(Album, on_delete=models.CASCADE)
file_type = models.CharField(max_length=50)
song_title = models.CharField(max_length=50)
is_favorite = models.BooleanField(default=False)
def __str__(self):
return self.song_title
# form submitted without action redirect to detail
def get_absolute_url(self):
return reverse('music:detail', kwargs={'pk': self.album.id})
Sunday 14 April 2019
django 27 update/delete user restriction
author is listed at footer of every post
if logged in as bob, can't edit/delete chuanshuo's post. Only the author can edit/delete his own post
edit own post no problem
#music/views
from django.views import generic
from .models import Album, Song
from django.views.generic.edit import CreateView, UpdateView, DeleteView, View
from django.urls import reverse_lazy
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, logout
from .forms import RegistrationForm, LoginForm
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.contrib import messages
class IndexView(generic.ListView):
template_name = 'music/index.html'
def get_queryset(self):
return Album.objects.all()
class DetailView(generic.DetailView):
model = Album
template_name = 'music/detail.html'
#protected route, require authentication
class AlbumCreate(LoginRequiredMixin, CreateView):
model = Album
fields = ['artist', 'album_title', 'genre', 'album_logo']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
class AlbumUpdate(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Album
fields = ['artist', 'album_title', 'genre', 'album_logo']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def test_func(self):
album = self.get_object()
if self.request.user == album.author:
return True
return False
class AlbumDelete(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Album
#redirect to home page after delete
success_url = reverse_lazy('music:index')
def test_func(self):
album = self.get_object()
if self.request.user == album.author:
return True
return False
#protected route, require authentication
class SongCreate(LoginRequiredMixin, CreateView):
model = Song
fields = ['file_type', 'song_title', 'is_favorite']
#overwrite field['album']
def form_valid(self, form):
form.instance.album = Album.objects.get(pk=self.kwargs['album_pk'])
return super().form_valid(form)
#pass variable song_album to song_form template
def get_context_data(self, **kwargs):
context = super(SongCreate, self).get_context_data(**kwargs)
song_album = Album.objects.get(pk=self.kwargs['album_pk']).album_title
context.update({'song_album':song_album})
return context
class SongUpdate(LoginRequiredMixin, UpdateView):
model = Song
fields = ['file_type', 'song_title', 'is_favorite']
# overwrite field['album']
def form_valid(self, form):
form.instance.album = Album.objects.get(pk=self.kwargs['album_pk'])
return super().form_valid(form)
# pass variable song_album to song_form template
def get_context_data(self, **kwargs):
context = super(SongUpdate, self).get_context_data(**kwargs)
song_album = Album.objects.get(pk=self.kwargs['album_pk']).album_title
context.update({'song_album': song_album})
return context
class SongDelete(LoginRequiredMixin, DeleteView):
model = Song
#redirect to detail page after delete
def get_success_url(self):
return reverse_lazy('music:detail',kwargs={'pk': self.kwargs['album_pk']})
#user registration
class UserRegistration(View):
form_class = RegistrationForm
template_name = 'music/registration_form.html'
def get(self, request):
#get request, display UserForm with empty fields
form = self.form_class(None)
return render(request, self.template_name, {'form': form})
def post(self, request):
form = self.form_class(request.POST)
if form.is_valid():
user = form.save(commit=False)
#clearned/normalized data
password = form.cleaned_data['password']
user.set_password(password)
user.save()
messages.success(request, 'registration successful')
return redirect('music:login')
return render(request, self.template_name, {'form': form})
#user login
class UserLogin(View):
form_class = LoginForm
template_name = 'music/login_form.html'
def get(self, request):
#get request, display UserForm with empty fields
form = self.form_class(None)
return render(request, self.template_name, {'form': form})
def post(self, request):
form = self.form_class(request.POST)
if form.is_valid():
#clearned/normalized data
username = form.cleaned_data['username']
password = form.cleaned_data['password']
#returns User objects if credentials are correct
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
return redirect('music:index')
messages.error(request, 'error logging in')
return render(request, self.template_name, {'form': form})
def UserLogout(request):
logout(request)
return redirect('music:index')
from django.views import generic
from .models import Album, Song
from django.views.generic.edit import CreateView, UpdateView, DeleteView, View
from django.urls import reverse_lazy
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, logout
from .forms import RegistrationForm, LoginForm
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.contrib import messages
class IndexView(generic.ListView):
template_name = 'music/index.html'
def get_queryset(self):
return Album.objects.all()
class DetailView(generic.DetailView):
model = Album
template_name = 'music/detail.html'
#protected route, require authentication
class AlbumCreate(LoginRequiredMixin, CreateView):
model = Album
fields = ['artist', 'album_title', 'genre', 'album_logo']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
class AlbumUpdate(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Album
fields = ['artist', 'album_title', 'genre', 'album_logo']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def test_func(self):
album = self.get_object()
if self.request.user == album.author:
return True
return False
class AlbumDelete(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Album
#redirect to home page after delete
success_url = reverse_lazy('music:index')
def test_func(self):
album = self.get_object()
if self.request.user == album.author:
return True
return False
#protected route, require authentication
class SongCreate(LoginRequiredMixin, CreateView):
model = Song
fields = ['file_type', 'song_title', 'is_favorite']
#overwrite field['album']
def form_valid(self, form):
form.instance.album = Album.objects.get(pk=self.kwargs['album_pk'])
return super().form_valid(form)
#pass variable song_album to song_form template
def get_context_data(self, **kwargs):
context = super(SongCreate, self).get_context_data(**kwargs)
song_album = Album.objects.get(pk=self.kwargs['album_pk']).album_title
context.update({'song_album':song_album})
return context
class SongUpdate(LoginRequiredMixin, UpdateView):
model = Song
fields = ['file_type', 'song_title', 'is_favorite']
# overwrite field['album']
def form_valid(self, form):
form.instance.album = Album.objects.get(pk=self.kwargs['album_pk'])
return super().form_valid(form)
# pass variable song_album to song_form template
def get_context_data(self, **kwargs):
context = super(SongUpdate, self).get_context_data(**kwargs)
song_album = Album.objects.get(pk=self.kwargs['album_pk']).album_title
context.update({'song_album': song_album})
return context
class SongDelete(LoginRequiredMixin, DeleteView):
model = Song
#redirect to detail page after delete
def get_success_url(self):
return reverse_lazy('music:detail',kwargs={'pk': self.kwargs['album_pk']})
#user registration
class UserRegistration(View):
form_class = RegistrationForm
template_name = 'music/registration_form.html'
def get(self, request):
#get request, display UserForm with empty fields
form = self.form_class(None)
return render(request, self.template_name, {'form': form})
def post(self, request):
form = self.form_class(request.POST)
if form.is_valid():
user = form.save(commit=False)
#clearned/normalized data
password = form.cleaned_data['password']
user.set_password(password)
user.save()
messages.success(request, 'registration successful')
return redirect('music:login')
return render(request, self.template_name, {'form': form})
#user login
class UserLogin(View):
form_class = LoginForm
template_name = 'music/login_form.html'
def get(self, request):
#get request, display UserForm with empty fields
form = self.form_class(None)
return render(request, self.template_name, {'form': form})
def post(self, request):
form = self.form_class(request.POST)
if form.is_valid():
#clearned/normalized data
username = form.cleaned_data['username']
password = form.cleaned_data['password']
#returns User objects if credentials are correct
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
return redirect('music:index')
messages.error(request, 'error logging in')
return render(request, self.template_name, {'form': form})
def UserLogout(request):
logout(request)
return redirect('music:index')
-----------------------------------------------
#music/template/index
{% extends 'music/base.html' %}
{% block title %}Albums{% endblock %}
{% block body %}
<div class="container-fluid">
<div class="row">
{% for album in object_list %}
<div class="col" style="margin-left: auto; margin-right: auto; margin-top: 5px; margin-bottom: 5px;">
<div class="card" style="width: 200px;">
<img src="{{album.album_logo.url}}" class="card-img-top" style="height: 150px" alt="{{album.album_title}}">
<div class="card-body">
<div style="height: 40px"><h5 class="card-title">Album: {{album.album_title}}</h5></div>
<div style="height: 40px"><p class="card-text">Artist: {{album.artist}}</p></div>
<!-- Detail -->
<a href="{% url 'music:detail' album.id %}" class="btn btn-primary btn-sm">View Detail</a>
<!-- Edit -->
<a class="btn btn-default btn-sm" href="{% url 'music:album-update' album.id %}">
<span class="glyphicon glyphicon-pencil"></span>
</a>
<!-- Delete -->
<a class="btn btn-default btn-sm" href="{% url 'music:album-delete' album.id %}">
<span class="glyphicon glyphicon-trash"></span>
</a>
</div>
<div class="card-footer text-muted">
Created by {{album.author}} @ {{album.date_posted}}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}
Subscribe to:
Posts (Atom)