はじめに
前回(ユーザ情報編)で使用したプロジェクトを引き継いで作成する。
開発環境
・Windows10 professional 64bit
・Python 3.7.1
・Django 2.1.2
・MySQL 8.0.12
プロジェクト管理
ナビゲーションドロワーの作成
前準備
jQueryをつかうので,公式からダウンロードして,static
フォルダ内にjs
フォルダを作りそこに格納する
下記のJavaScriptとCSSを作成する
$(function($) {
WindowHeight = $(window).height()
$(document).ready(function() {
$('.barbtn').click(function(){
if($('.drawr').is(":animated")){
return false;
}else{
$('.drawr').animate({width:'toggle'});
$(this).toggleClass('peke');
return false;
}
});
});
//別領域をクリックでメニューを閉じる
$(document).click(function(event) {
if (!$(event.target).closest('.drawr').length) {
$('.barbtn').removeClass('peke');
$('.drawr').hide();
}
});
});
.peke {
background-position: -35px 0;
}
.drawr {
display: none;
background-color:rgba(0, 0, 0, 0.404);
position: absolute;
top: 0px;
left:0;
width:250px;
padding:60px 0 20px 20px;
z-index: 100;
}
.barmenu{
width:220px;
height:50px;
margin-top:10px;
}
.barmenu a{
position: relative;
display: block;
font-weight: bold;
text-decoration: none;
color: #FFF;
text-shadow: 0 0 5px rgba(255, 255, 255, 0.0);
transition: .2s;
}
.barmenu a:hover {
text-shadow: -6px 0px 15px rgba(255, 255, 240, 0.83), 6px 0px 15px rgba(255, 255, 240, 0.83);
transition: .2s;
}
.top{
font-size: 32px;
position: relative;
display: block;
font-weight: bold;
text-decoration: none;
color: #FFF;
text-shadow: -6px 0px 15px rgba(5, 51, 255, 0.83), 6px 0px 15px rgba(255, 255, 240, 0.83);
height: 100px;
}
HTML修正
JavaScriptとCSSを元にbase.html
を修正する
<!DOCTYPE html>
<html lang='ja'>
<head>
{% load staticfiles %}
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<link rel="shortcut icon" type="image/x-icon" href="{% static 'img/c0063_6_2.png' %}">
<title>{% block title %}{% endblock %}</title>
<link href="{% static 'bootstrap/dist/css/bootstrap.css' %}" rel="stylesheet">
<link href="{% static 'bootstrap/dist/css/bootstrap.min.css' %}" rel="stylesheet">
<link href="{% static 'bootstrap/dist/css/bootstrap-grid.css' %}" rel="stylesheet">
<link href="{% static 'bootstrap/dist/css/bootstrap-grid.min.css' %}" rel="stylesheet">
<link href="{% static 'bootstrap/dist/css/bootstrap-reboot.css' %}" rel="stylesheet">
<link href="{% static 'bootstrap/dist/css/bootstrap-reboot.min.css' %}" rel="stylesheet">
<link href="{% static 'bootstrap/docs/4.0/examples/dashboard/dashboard.css' %}" rel="stylesheet">
<link href="{% static 'css/adjustment.css' %}" rel="stylesheet" type="text/css">
<link href="{% static 'css/navibar.css' %}" rel="stylesheet" type="text/css"> <!-- 追加 -->
<script type="text/javascript" src="{% static 'js/jquery-3.3.1.min.js' %}"></script> <!-- 追加 -->
<script src="{% static 'bootstrap/assets/js/vendor/anchor.min.js' %}"></script>
<script src="{% static 'bootstrap/assets/js/vendor/clipboard.min.js' %}"></script>
<script src="{% static 'bootstrap/assets/js/vendor/holder.min.js' %}"></script>
<script src="{% static 'bootstrap/assets/js/vendor/popper.min.js' %}"></script>
<script src="{% static 'bootstrap/dist/js/bootstrap.bundle.js' %}"></script>
<script src="{% static 'bootstrap/dist/js/bootstrap.bundle.min.js' %}"></script>
<script src="{% static 'bootstrap/dist/js/bootstrap.js' %}"></script>
<script src="{% static 'bootstrap/dist/js/bootstrap.min.js' %}"></script>
<script src="{% static 'bootstrap/js/dist/alert.js' %}"></script>
<script src="{% static 'bootstrap/js/dist/button.js' %}"></script>
<script src="{% static 'bootstrap/js/dist/carousel.js' %}"></script>
<script src="{% static 'bootstrap/js/dist/collapse.js' %}"></script>
<script src="{% static 'bootstrap/js/dist/dropdown.js' %}"></script>
<script src="{% static 'bootstrap/js/dist/index.js' %}"></script>
<script src="{% static 'bootstrap/js/dist/modal.js' %}"></script>
<script src="{% static 'bootstrap/js/dist/scrollspy.js' %}"></script>
<script src="{% static 'bootstrap/js/dist/tab.js' %}"></script>
<script src="{% static 'bootstrap/js/dist/tooltip.js' %}"></script>
<script src="{% static 'bootstrap/js/dist/util.js' %}"></script>
<script src="{% static 'js/navibar.js' %}"></script> <!-- 追加 -->
</head>
<body>
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
<a class="navbar-brand col-sm-3 col-md-2 mr-0 barbtn" href="">TaskMan</a> <!-- 変更 -->
<div class="form-control form-control-dark w-100">{% block mainT %}{% endblock %}</div>
<div class="btn-group">
<div class="btn dropdown-toggle naviDrop" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{% if user.is_authenticated %}
User : {{ user.get_username }}
{% if user.icon.url != "" %}
<img src="{{ user.icon.url }}" style="height: 20px; width: 20px;">
{% else %}
<img src="/static/img/c0063_6_2.png" style="height: 20px; width: 20px;">
{% endif%}
{% else %}
User
{% endif %}
</div>
{% if user.is_authenticated %}
<div class="dropdown-menu dropdown-menu-right" >
<a class="dropdown-item" href="{% url 'accounts:profile' user.pk %}">ユーザ情報</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="/logout/">ログアウト</a>
</div>
{% endif %}
</div>
</nav>
<!-- 追加 start -->
<div class="drawr">
<div class="barmenu top">TaskManager</div>
<div class="barmenu"><a href="/">トップページ</a></div>
<div class="barmenu"><a href="#">タスク</a></div>
<div class="barmenu"><a href="#">ガントチャート</a></div>
<div class="barmenu"><a href="#">チャット</a></div>
</div>
<!-- 追加 end -->
{% block body %}{% endblock %}
</body>
</html>
画面確認
右上のTaskMan
を押すとナビゲーションドロワーが表示されるのを確認する
プロジェクトの作成
modelsの作成
プロジェクトごとにタスクを管理していきたいので,プロジェクトのモデルを作成する
from django.db import models
from accounts.models import User
""" プロジェクトモデル """
class Project(models.Model):
project_cd = models.AutoField(
primary_key=True
)
name = models.CharField(
max_length=25,
verbose_name='プロジェクト名'
)
leader = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='leader',
verbose_name='プロジェクトリーダ'
)
start_date = models.DateField(
null=True,
blank=True,
verbose_name='開始日'
)
end_date = models.DateField(
null=True,
blank=True,
verbose_name='終了日'
)
details = models.CharField(
max_length=200,
null=True,
blank=True,
verbose_name='詳細'
)
update_date = models.DateField(
auto_now=True
)
is_delete = models.BooleanField(
default=False
)
また,プロジェクトとユーザ,プロジェクトとタスクを紐づけるモデルを作成
""" プロジェクト To ユーザ (1 対 多) """
class ProjectToUsers(models.Model):
project_cd = models.ForeignKey(
Project,
on_delete=models.CASCADE
)
user_cd = models.ForeignKey(
User,
on_delete=models.CASCADE
)
プロジェクト画面の作成
タスクをまとめるプロジェクト一覧用の画面を作成する
URL設定
プロジェクトのurls.pyを編集する
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('accounts.urls')),
path('task/', include('task.urls')), # 追加
]
この設定によりtask/
で始まるURLはtaskアプリケーション
のurls.py
を参照するようになる
なので,task
のurls.py
を編集する
from django.urls import path
from . import views
app_name = 'task'
urlpatterns = [
path('', views.taskTop.as_view(), name='task_top'),
]
`views.py'も編集する
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.views import generic
from django.contrib.sites.shortcuts import get_current_site
from django.shortcuts import redirect, resolve_url
from django.contrib.auth.decorators import login_required
from django.contrib.auth import get_user_model
from django.db import models
from .models import ProjectToUsers, Project
User = get_user_model()
""" トップページ """
class taskTop(LoginRequiredMixin, generic.TemplateView):
template_name = 'task/task_top.html'
redirect_field_name = 'redirect_to'
def get_context_data(self, **kwargs):
user = self.request.user
context = super().get_context_data(**kwargs)
project_user = ProjectToUsers.objects.filter(user_cd=user.use_cd)
leader = Project.objects.filter(leader=user.use_cd, is_delete=0)
if len(project_user) > 0:
context['member'] = []
for person in project_user:
member = Project.objects.filter(project_cd=person.project_cd.pk, is_delete=0)
context['member'].extend(member)
else:
context['member'] = None
context['leader'] = leader if len(leader) > 0 else None
return context
ユーザがリーダのプロジェクトと,ユーザが参加しているプロジェクトを取得しhtmlに渡す処理を行う
HTML作成
templatesファイルにhtmlを作成する
{% extends "base.html" %}
{% block title %} TaskMan {% endblock %}
{% block mainT %} タスク {% endblock %}
{% block body %}
<div class="leftArea">
<h2 class="eleTitle">主導プロジェクト</h2>
{% if leader != None %}
{% for lead in leader %}
<a class="project_area" href="{% url 'task:our_project' lead.pk %}">
<div>
<h4>{{lead.name}}</h4>
<p>リーダ : {{lead.leader}}</p>
{% if lead.start_date != None %}
<p>開始日 : {{lead.start_date}}</p>
{% endif %}
{% if lead.end_date != None %}
<p>終了日 : {{lead.end_date}}</p>
{% endif %}
{% if lead.details != None %}
<p>詳細 : {{lead.details}}</p>
{% endif %}
</div>
</a>
{% endfor %}
{% else %}
<h4>プロジェクトがありません</h4>
{% endif %}
<a class="btn btn-outline-secondary" href="/task/build_project/">プロジェクト作成</a>
</div>
<div class="rightArea">
<h2 class="eleTitle">参加プロジェクト</h2>
{% if member != None %}
{% for men in member %}
<a class="project_area" href="{% url 'task:our_project' men.pk %}">
<div>
<h4>{{men.name}}</h4>
<p>リーダ : {{men.leader}}</p>
{% if men.start_date != None %}
<p>開始日 : {{men.start_date}}</p>
{% endif %}
{% if men.end_date != None %}
<p>終了日 : {{men.end_date}}</p>
{% endif %}
{% if men.details != None %}
<p>詳細 : {{men.details}}</p>
{% endif %}
</div>
</a>
{% endfor %}
{% else %}
<h4>プロジェクトがありません</h4>
{% endif %}
</div>
{% endblock %}
cssを追加する
.leftArea {
width: 50%;
height: 100%;
padding: 20px;
border-right: 1px;
float:left;
word-wrap: break-word;
}
.rightArea {
width: 50%;
height: 100%;
padding: 20px;
float: right;
word-wrap: break-word;
}
.eleTitle {
padding-bottom:10px;
border-bottom: solid 5px rgb(36, 0, 119);
}
.project_area {
color:black;
}
.project_area:hover{
text-decoration: none;
}
.project_area div{
border-radius:20px;
padding:10px;
margin-bottom:20px;
width: 80%;
background-color: rgb(235, 244, 255);
-webkit-transition: all 0.5s;
transition: all 0.5s;
}
.project_area div:hover{
background-color: #c3dfff; /*ボタン色*/
}
.project_area h4{
border-bottom: solid 3px rgb(3, 0, 161);
}
画面の作成が完了
画面確認
新規プロジェクトの作成
次はタスクを管理するプロジェクトを作成する処理を作る
FORM・URL設定
登録用のフォームとURLを作成する
from django import forms
from .models import Project, ProjectToUsers
""" プロジェクト作成 """
class ProjectCreate(forms.ModelForm):
class Meta:
model = Project
fields = (
'name', 'leader',
'start_date', 'end_date',
'details'
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs['class'] = 'form-control'
self.fields['leader'].widget.attrs['hidden'] = 'true'
self.fields['start_date'].widget.input_type = "date"
self.fields['end_date'].widget.input_type="date"
登録者をリーダとするので,leader
は変更できないようにhidden
にし,日付は`date型にしてカレーコントロールが出来るようにする
(date型のカレンダーコントロールはIE非対応)
from django.urls import path
from . import views
app_name = 'task'
urlpatterns = [
path('', views.taskTop.as_view(), name='task_top'),
path('build_project/', views.BuildProject.as_view(), name='build_project'), # 追加
]
from .forms import ProjectCreate
""" プロジェクト作成 """
class BuildProject(LoginRequiredMixin, generic.CreateView):
model = Project
form_class = ProjectCreate
success_url ='/task/'
template_name = 'task/build_project.html'
def get_initial(self):
leader = self.request.user.pk
return {'leader': leader,}
def get_initial
でleader
の初期値を作成者にする
画面確認
プロジェクト一覧画面から作成ボタンを押す
入力して登録をすると一覧画面に表示される
プロジェクトページの作成
作成したプロジェクトのタスクやメンバーなどを表示するページを作成する
URL設定
ページのURLを設定する
from django.urls import path
from . import views
app_name = 'task'
urlpatterns = [
path('', views.taskTop.as_view(), name='task_top'),
path('build_project/', views.BuildProject.as_view(), name='build_project'),
path('ProjectPage/<int:pk>/', views.ProjectPage.as_view(), name='our_project'), # 追加
""" プロジェクト限定 """
class ProjectUserOnlyMixin(UserPassesTestMixin):
raise_exception = True
def test_func(self):
is_return = True
user = self.request.user
l_result = Project.objects.filter(leader=user.use_cd, project_cd=self.kwargs["pk"])
m_result = ProjectToUsers.objects.filter(project_cd=self.kwargs["pk"], user_cd=self.request.user.pk)
p_result = Project.objects.filter(project_cd=self.kwargs["pk"], is_delete=0)
if len(l_result) == 0 and len(m_result) == 0:
is_return = False
if len(p_result) == 0:
is_return = False
return is_return
""" プロジェクトページ """
class ProjectPage(ProjectUserOnlyMixin, generic.TemplateView):
template_name = 'task/our_project.html'
def get_context_data(self, **kwargs):
user = self.request.user
context = super().get_context_data(**kwargs)
project = Project.objects.filter(project_cd=self.kwargs["pk"])
p_user = ProjectToUsers.objects.filter(project_cd=self.kwargs["pk"])
if len(p_user) > 0:
context['member'] = []
for person in p_user:
member = User.objects.filter(pk=person.user_cd.pk)
context['member'].extend(member)
else:
context['member'] = None
context['project'] = project if len(project) > 0 else None
context['task'] = None
return context
プロジェクトに参加していないメンバーが入れないようにUserPassesTestMixin
を継承したProjectUserOnlyMixin
を作成し参加しているメンバーだけに画面が表示されるようにする
get_context_data
でプロジェクトの情報をtemplatesHTMLに渡す
HTML作成
プロジェクト画面のHTMLを作成する
{% extends "base.html" %}
{% block title %} TaskMan {% endblock %}
{% block mainT %} {{ project.0.name }} {% endblock %}
{% block body %}
<nav class=" navbar-dark bg-dark flex-md-nowrap p-0 header">
<a class="navbar-brand col-md-1 mr-0" href="/task/">戻る</a>
<a class="navbar-brand col-md-2 mr-0" href="{% url 'task:our_project_detail' project.0.pk %}"> プロジェクト詳細 </a>
<a class="navbar-brand col-md-2 mr-0" href="#">タスク追加</a>
<div class="btn-group">
<div class="btn dropdown-toggle naviDrop" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
メンバー
</div>
<div class="dropdown-menu dropdown-menu-right" >
<a class="dropdown-item" href="#"> {{ project.0.leader }} </a>
{% if member != None %}
<div class="dropdown-divider"></div>
{% for person in member %}
<a class="dropdown-item" href="#"> {{ person }} </a>
{% endfor %}
{% endif %}
</div>
</div>
</div>
</nav>
{% if task == None %}
<div style="margin:20px"></div>
<h4>タスクがありません</h4>
</div>
{% endif %}
{% endblock %}
CSSも追加する
.header {
width: 99%;
margin: 3px auto;
box-shadow: 0 3px 5px rgba(95, 95, 95, 0.22);
}
画面確認
プロジェクト詳細・更新・削除
プロジェクトの詳細画面と更新処理と削除処理を作る
FORM・URL設定
更新フォームと削除フォームを作成する
""" プロジェクト更新 """
class ProjectUpdate(forms.ModelForm):
class Meta:
model = Project
fields = (
'name',
'start_date', 'end_date',
'details'
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs['class'] = 'form-control'
self.fields['start_date'].widget.input_type = "date"
self.fields['end_date'].widget.input_type="date"
""" プロジェクト削除 """
class ProjectDelete(forms.ModelForm):
class Meta:
model = Project
fields = (
'is_delete',
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['is_delete'].widget.attrs['hidden'] = 'true'
URLの設定をする
from django.urls import path
from . import views
app_name = 'task'
urlpatterns = [
path('', views.taskTop.as_view(), name='task_top'),
path('build_project/', views.BuildProject.as_view(), name='build_project'),
path('ProjectPage/<int:pk>/', views.ProjectPage.as_view(), name='our_project'),
path('ProjectPage/<int:pk>/detail', views.ProjectDetail.as_view(), name='our_project_detail'), # 追加
path('ProjectPage/<int:pk>/update', views.ProjectUpdate.as_view(), name='our_project_update'), # 追加
path('ProjectPage/<int:pk>/delete', views.ProjectDelete.as_view(), name='our_project_delete'), # 追加
]
""" プロジェクトリーダ限定 """
class ProjectLeaderOnlyMixin(UserPassesTestMixin):
raise_exception = True
def test_func(self):
is_return = True
user = self.request.user
l_result = Project.objects.filter(leader=user.use_cd, project_cd=self.kwargs["pk"])
p_result = Project.objects.filter(project_cd=self.kwargs["pk"], is_delete=0)
if len(l_result) == 0:
is_return = False
if len(p_result) == 0:
is_return = False
return is_return
""" プロジェクト情報 """
class ProjectDetail(ProjectUserOnlyMixin, generic.DetailView):
model = Project
template_name = 'task/project_detail.html'
def get_context_data(self, **kwargs):
user = self.request.user
context = super().get_context_data(**kwargs)
p_user = ProjectToUsers.objects.filter(project_cd=self.kwargs["pk"])
project = Project.objects.filter(project_cd=self.kwargs["pk"], is_delete=0)
context['member'] = p_user if len(p_user) > 0 else None
context['project'] = project if len(project) > 0 else None
return context
""" プロジェクト更新ページ """
class ProjectUpdate(ProjectUserOnlyMixin, generic.UpdateView):
model = Project
template_name = 'task/project_update.html'
form_class = ProjectUpdate
def get_success_url(self):
return resolve_url('task:our_project_detail', pk=self.kwargs["pk"])
""" プロジェクト削除ページ """
class ProjectDelete(ProjectLeaderOnlyMixin, generic.UpdateView):
model = Project
template_name = 'task/project_delete.html'
form_class = ProjectDelete
def get_context_data(self, **kwargs):
user = self.request.user
context = super().get_context_data(**kwargs)
p_user = ProjectToUsers.objects.filter(project_cd=self.kwargs["pk"])
project = Project.objects.filter(project_cd=self.kwargs["pk"], is_delete=0)
if len(p_user) > 0:
context['member'] = []
for person in p_user:
member = User.objects.filter(pk=person.user_cd.pk)
context['member'].extend(member)
else:
context['member'] = None
context['project'] = project if len(project) > 0 else None
return context
def get_initial(self):
return {'is_delete': True,}
def get_success_url(self):
return resolve_url('task:task_top')
アクセスしたユーザがリーダかを判定する'ProjectLeaderOnlyMixin'を作成し,削除系がリーダにしかできないようにする
HTML作成
詳細ページ,更新ページ,削除ページそれぞれのHTMLを準備する
・詳細ページ
{% extends "base.html" %}
{% block title %} TaskMan {% endblock %}
{% block mainT %} {{ project.0.name }} 詳細情報 {% endblock %}
{% block body %}
<div style="margin:20px">
<table class="table">
<tbody>
<tr>
<th>プロジェクト名</th>
<td>{{ project.0.name }}</td>
</tr>
<tr>
<th>開始日</th>
<td>{{ project.0.start_date }}</td>
</tr>
<tr>
<th>終了日</th>
<td>{{ project.0.end_date }}</td>
</tr>
<tr>
<th>詳細</th>
<td>{{ project.0.details }}</td>
</tr>
<tr>
<th>リーダ</th>
<td>{{ project.0.leader }}</td>
</tr>
<tr>
<th>メンバー</th>
<td>
{% if member != None %}
<table class="no_border">
{% for perspn in member %}
<tr>
<td>
{{ perspn.user_cd }}
</td>
</tr>
{% endfor %}
</table>
{% else %}
メンバーはいません
{% endif %}
</td>
</tr>
</tbody>
</table>
<a class="btn btn-outline-secondary" href="{% url 'task:update_member' project.0.pk %}">メンバー更新</a>
<hr>
<a href="{% url 'task:our_project' project.0.pk %}" class="btn btn-primary" style="width: 120px; margin-right:10px;">戻る</a>
<a href="{% url 'task:our_project_update' project.0.pk %}" class="btn btn-primary ">変更</a>
{% if user == project.0.leader %}
<a href="{% url 'task:our_project_delete' project.0.pk %}" class="delete-btn btn btn-primary">削除</a>
{% endif %}
</div>
{% endblock %}
・更新ページ
{% extends "base.html" %}
{% block title %} TaskMan {% endblock %}
{% block mainT %} 更新 {% endblock %}
{% block body %}
<div style="margin:20px">
<form action="" method="POST">
{{ form.non_field_errors }}
<table class="table">
<tbody>
{% for field in form %}
<tr>
<th><label for="{{ field.id_for_label }}">{{ field.label }}</label></th>
<td>{{ field }} <span style="color:red">{{ field.errors }}<span></td>
</tr>
{% endfor %}
</tbody>
</table>
{% csrf_token %}
<button type="submit" class="btn btn-success" >更新</button>
<a href="{% url 'task:our_project_detail' project.pk %}" class="btn btn-primary">戻る</a>
</form>
</div>
{% endblock %}
・削除ページ
{% extends "base.html" %}
{% block title %} TaskMan {% endblock %}
{% block mainT %} 削除 {% endblock %}
{% block body %}
<div style="margin:20px">
<table class="table">
<tbody>
<tr>
<th>プロジェクト名</th>
<td>{{ project.0.name }}</td>
</tr>
<tr>
<th>開始日</th>
<td>{{ project.0.start_date }}</td>
</tr>
<tr>
<th>終了日</th>
<td>{{ project.0.end_date }}</td>
</tr>
<tr>
<th>詳細</th>
<td>{{ project.0.details }}</td>
</tr>
<tr>
<th>リーダ</th>
<td>{{ project.0.leader }}</td>
</tr>
{% if member != None %}
<tr>
<th>メンバー</th>
<td>
<table class="no_border">
{% for person in member %}
<Tr>
<td>{{ person.username }}</td>
</Tr>
{% endfor %}
</table>
</td>
</tr>
{% endif %}
</tbody>
</table>
<br>
<h2 style="color:rgba(255, 44, 44, 0.753);">本当に削除してよろしいですか?</h2>
<br>
<form action="" method="POST">
{{ form.non_field_errors }}
{% for field in form %}
{{ field }}
{% endfor %}
{% csrf_token %}
<br>
<a href="{% url 'task:our_project_detail' project.0.pk %}" class="btn btn-primary">戻る</a>
<button type="submit" class="delete-btn btn btn-success">削除</button>
</form>
{% endblock %}
CSSも追加する
.delete-btn{
background-color: red;
border: solid 1px black;
}
.delete-btn:hover{
background-color: rgb(145, 0, 0);
}
.member_list td, .member_list th{
padding:5px 10px;
border-bottom: solid 1px black;
}
table.no_border, .no_border thead, .no_border tbody, .no_border tr, .no_border th, .no_border td{
border: none;
}
画面確認
プロジェクト画面のプロジェクト詳細
をクリックすると詳細画面が表示される
変更ボタンを押すと更新画面が表示されプロジェクトデータを更新することができる
メンバー管理
プロジェクトのメンバーを追加・削除が出来るようにする
modelsの作成
ユーザとプロジェクトを結びつけるモデルを作成する
""" プロジェクト To タスク (1 対 多) """
class ProjectToTask(models.Model):
project_cd = models.ForeignKey(
Project,
on_delete=models.CASCADE
)
task_cd = models.ForeignKey(
Task,
on_delete=models.CASCADE
)
メンバー追加・削除
FORM・URL設定
メンバーを追加・削除する際のフォームの作成とURLの設定を行う
""" メンバー追加 """
class AddProjectMember(forms.ModelForm):
class Meta:
model = ProjectToUsers
fields = (
'project_cd',
'user_cd'
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
where = []
for field in self.fields.values():
field.widget.attrs['class'] = 'form-control'
self.fields['project_cd'].widget.attrs['hidden'] = 'true'
leader = Project.objects.filter(project_cd=self.initial["project_cd"])
in_member = ProjectToUsers.objects.filter(project_cd=self.initial["project_cd"])
staff = User.objects.filter(is_staff=1)
non_active = User.objects.filter(is_active=0)
where.append(leader[0].leader_id)
for in_men in in_member:
where.append(in_men.user_cd_id)
for s in staff:
where.append(s.pk)
for non in non_active:
where.append(non.pk)
result = User.objects.exclude(use_cd__in=where)
self.fields['user_cd'].queryset = result
project_cd
は変更出来ないように非表示にして,追加するユーザもリーダや既にメンバーなどが追加されないようにリストから除外する。
また,superuser
やactive
でないユーザも除外する
削除のフォームは作成しない
from django.urls import path
from . import views
app_name = 'task'
urlpatterns = [
path('', views.taskTop.as_view(), name='task_top'),
path('build_project/', views.BuildProject.as_view(), name='build_project'),
path('ProjectPage/<int:pk>/', views.ProjectPage.as_view(), name='our_project'),
path('ProjectPage/<int:pk>/detail', views.ProjectDetail.as_view(), name='our_project_detail'),
path('ProjectPage/<int:pk>/update', views.ProjectUpdate.as_view(), name='our_project_update'),
path('ProjectPage/<int:pk>/delete', views.ProjectDelete.as_view(), name='our_project_delete'),
path('ProjectPage/<int:pk>/update_member', views.UpdateProjectMember.as_view(), name='update_member'), # 追加
path('ProjectPage/<int:pk>/delete_member', views.ProjectDeleteMember.as_view(), name='our_project_delete_member'), # 追加
]
""" プロジェクトメンバー追加 """
""" プロジェクトメンバー更新 """
class UpdateProjectMember(ProjectUserOnlyMixin, generic.CreateView):
model = ProjectToUsers
template_name = "task/update_member.html"
form_class = AddProjectMember
def get_context_data(self, **kwargs):
user = self.request.user
context = super().get_context_data(**kwargs)
p_user = ProjectToUsers.objects.filter(project_cd=self.kwargs["pk"])
project = Project.objects.filter(project_cd=self.kwargs["pk"], is_delete=0)
context['member'] = p_user if len(p_user) > 0 else None
context['project'] = project if len(project) > 0 else None
return context
def get_success_url(self):
return resolve_url('task:update_member', pk=self.kwargs["pk"])
def get_initial(self):
pc = self.kwargs["pk"]
return {'project_cd': pc,}
""" プロジェクトメンバー削除 """
class ProjectDeleteMember(ProjectLeaderOnlyMixin, generic.DeleteView):
model = ProjectToUsers
def delete(self, *args, **kwargs):
delete=1
delete_ids = self.request.POST.getlist('delete_ids')
if delete_ids:
ProjectToUsers.objects.filter(id__in=delete_ids).delete()
return redirect('task:update_member', pk=self.kwargs["pk"])
UpdateProjectMember
クラスのdef get_initial(self)
でproject_cd
の初期値を設定している
ProjectDeleteMember
クラスのdef delete(self, *args, **kwargs)
では選択されたものを削除する処理を行っている
HTML作成
メンバーを追加,削除するページを作成する
{% extends "base.html" %}
{% block title %} TaskMan {% endblock %}
{% block mainT %} メンバー更新 {% endblock %}
{% block body %}
<div style="margin:50px 100px;">
<form method="post" action="{% url 'task:our_project_delete_member' project.0.pk%}" >
<table class="table">
<tbody>
<tr>
<th>リーダ</th>
<td>{{ project.0.leader }}</td>
</tr>
<tr>
<th>メンバー</th>
<td>
{% if member != None %}
<table class="no_border">
<thead class="MDC">
<tr>
<th>ユーザ名</th>
<th>選択</th>
</tr>
</thead>
{% for perspn in member %}
<tr>
<td>
{{ perspn.user_cd }}
</td>
<td class="MDC">
<input type="checkbox" class="CCB" name="delete_ids" value="{{ perspn.id }}">
</td>
</tr>
{% endfor %}
</table>
{% else %}
メンバーはいません
{% endif %}
</td>
</tr>
</tbody>
</table>
<a href="{% url 'task:our_project_detail' project.0.pk %}" class="btn btn-primary MD">戻る</a>
<a class="btn btn-primary MD" style="color: white;" id="AMB">メンバー追加</a>
{% if member != None and user == project.0.leader %}
<a class="btn btn-primary MD MDB" style="color:white; margin-left: 10px;">メンバー退席</a>
<a class="btn btn-primary MDC MDCB" style="color:white; margin-left: 10px;">キャンセル</a>
<input class="delete-btn btn btn-primary MDC CPB" style="margin-left: 10px;" type="submit" value="退席">
{% csrf_token %}
{% endif %}
</form>
</div>
<div id="modal-member">
<form action="" method="POST">
{{ form.non_field_errors }}
{% for field in form %}
<div class="form-group">
{% if field.label != "Project cd" %}
<label for="{{ field.id_for_label }}">{{ field.label_tag }}</label>
{% endif %}
{{ field }}
<span style="color:red">{{ field.errors }}<span>
</div>
{% endfor %}
{% csrf_token %}
<button type="submit" class="btn btn-success btn-lg">追加</button>
<a class="btn btn-primary btn-lg" style="color: white;" id="AMCB">戻る</a>
</form>
</div>
{% endblock %}
プロジェクトのリーダの時だけメンバーを削除することができるようにする
CSS・JavaScript作成
ユーザを追加・削除する際に画面に動きを付けているので,画面を制御するJavaScriptとCSSを設定する
.member_list td, .member_list th{
padding:5px 10px;
border-bottom: solid 1px black;
}
table.no_border, .no_border thead, .no_border tbody, .no_border tr, .no_border th, .no_border td{
border: none;
}
/* モーダル 背景 */
#modal-bg {
display:none;
width:100%;
height:100%;
background-color: rgba(0,0,0,0.5);
position:fixed;
top:0;
left:0;
z-index: 1;
}
/* モーダル コンテンツエリア */
#modal-member {
display: none;
width: 50%;
height: 300px;
padding:50px;
border-radius:10px;
position: absolute;
left: 0px;
right: 0px;
top: 0px;
bottom: 0px;
margin: auto;
background-color: white;
position:fixed;
z-index: 2;
}
// メンバー削除チェックボックス表示
$(function () {
$('.MDC').hide();
$('.MDB').click(function () {
$('.MD').hide();
$('.MDC').show();
})
})
// メンバー削除チェックボックス非表示
$(function () {
$('.MDCB').click(function () {
$('.CCB').prop('checked', false);
$('.CPB').css('visibility', 'hidden');
$('.MDC').hide();
$('.MD').show();
})
})
// チェックボックスが選択されている場合のみボタンが活性状態になる
$(function() {
$('.CPB').css('visibility', 'hidden');
$('.CCB').change(function () {
if ($('.CCB:checked').length == 0) {
$('.CPB').css('visibility', 'hidden');
} else {
$('.CPB').css('visibility', 'visible');
}
})
})
// メンバー追加モーダル管理
$(function () {
//追加ボタンを押した際
$('#AMB').click(function () {
//背景を付ける
$("body").append('<div id="modal-bg"></div>');
//モーダルウィンドウを表示
$("#modal-bg,#modal-member").fadeIn("fast");
//画面の背景もしくは戻るボタンを押したらモーダルを閉じる
$('#modal-bg, #AMCB').click(function () {
$("#modal-bg,#modal-member").fadeOut("fast", function () {
$('#modal-bg').remove();
});
});
});
});
新しく作成したcssとjsをbase.html
のヘッダに追加する
<link href="{% static 'css/modal.css' %}" rel="stylesheet" type="text/css">
<script src="{% static 'js/modal.js' %}"></script>
<script src="{% static 'js/masta.js' %}"></script>
画面確認
今回はメンバー追加をするので新しくアカウントを作っておく
メンバーを追加・削除することができたのが確認できた
今回のまとめ
プロジェクトの更新・削除は苦労することなく作ることができたが,追加できるメンバーの絞り込みや,メンバーの削除に手間取ってしまった。原因としては,データベースにアクセスする際の条件がうまく設定できなかったので,pythonの勉強がまだまだ必要だということが分かった。
今回まででプロジェクトごとにユーザを限定出来るようになったので,次はプロジェクト内でタスクを作成し管理できるようにする
シリーズ
・Djangoでタスク管理アプリをつくりたい! 環境構築編
・Djangoでタスク管理アプリをつくりたい! ユーザ認証編
・Djangoでタスク管理アプリをつくりたい! ユーザ情報編
・Djangoでタスク管理アプリをつくりたい! プロジェクト管理編
・Djangoでタスク管理アプリをつくりたい! タスク管理編1