Merge branch 'release/1.1.0'
This commit is contained in:
commit
cba82b5165
2
Pipfile
2
Pipfile
@ -6,6 +6,8 @@ name = "pypi"
|
|||||||
[packages]
|
[packages]
|
||||||
django = "*"
|
django = "*"
|
||||||
django-anymail = {extras = ["mailgun"], version = "*"}
|
django-anymail = {extras = ["mailgun"], version = "*"}
|
||||||
|
celery = {extras = ["redis"], version = "*"}
|
||||||
|
django-celery-beat = "*"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
|
|
||||||
|
|||||||
132
Pipfile.lock
generated
132
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "88b6a9fdda6471bfc16c42a906945bb034d4b2d576f31179a72471296eac58cd"
|
"sha256": "ffe96f34ca738a35a283e9ac98b5f1a74609c67619f18e8eb7ba62967247766f"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -16,6 +16,14 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"default": {
|
"default": {
|
||||||
|
"amqp": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:03e16e94f2b34c31f8bf1206d8ddd3ccaa4c315f7f6a1879b7b1210d229568c2",
|
||||||
|
"sha256:493a2ac6788ce270a2f6a765b017299f60c1998f5a8617908ee9be082f7300fb"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.6'",
|
||||||
|
"version": "==5.0.6"
|
||||||
|
},
|
||||||
"asgiref": {
|
"asgiref": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9",
|
"sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9",
|
||||||
@ -24,6 +32,24 @@
|
|||||||
"markers": "python_version >= '3.6'",
|
"markers": "python_version >= '3.6'",
|
||||||
"version": "==3.4.1"
|
"version": "==3.4.1"
|
||||||
},
|
},
|
||||||
|
"billiard": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:299de5a8da28a783d51b197d496bef4f1595dd023a93a4f59dde1886ae905547",
|
||||||
|
"sha256:87103ea78fa6ab4d5c751c4909bcff74617d985de7fa8b672cf8618afd5a875b"
|
||||||
|
],
|
||||||
|
"version": "==3.6.4.0"
|
||||||
|
},
|
||||||
|
"celery": {
|
||||||
|
"extras": [
|
||||||
|
"redis"
|
||||||
|
],
|
||||||
|
"hashes": [
|
||||||
|
"sha256:8d9a3de9162965e97f8e8cc584c67aad83b3f7a267584fa47701ed11c3e0d4b0",
|
||||||
|
"sha256:9dab2170b4038f7bf10ef2861dbf486ddf1d20592290a1040f7b7a1259705d42"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==5.1.2"
|
||||||
|
},
|
||||||
"certifi": {
|
"certifi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee",
|
"sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee",
|
||||||
@ -39,6 +65,34 @@
|
|||||||
"markers": "python_version >= '3'",
|
"markers": "python_version >= '3'",
|
||||||
"version": "==2.0.3"
|
"version": "==2.0.3"
|
||||||
},
|
},
|
||||||
|
"click": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
|
||||||
|
"sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||||
|
"version": "==7.1.2"
|
||||||
|
},
|
||||||
|
"click-didyoumean": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:112229485c9704ff51362fe34b2d4f0b12fc71cc20f6d2b3afabed4b8bfa6aeb"
|
||||||
|
],
|
||||||
|
"version": "==0.0.3"
|
||||||
|
},
|
||||||
|
"click-plugins": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b",
|
||||||
|
"sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"
|
||||||
|
],
|
||||||
|
"version": "==1.1.1"
|
||||||
|
},
|
||||||
|
"click-repl": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:94b3fbbc9406a236f176e0506524b2937e4b23b6f4c0c0b2a0a83f8a64e9194b",
|
||||||
|
"sha256:cd12f68d745bf6151210790540b4cb064c7b13e571bc64b6957d98d120dacfd8"
|
||||||
|
],
|
||||||
|
"version": "==0.2.0"
|
||||||
|
},
|
||||||
"django": {
|
"django": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:3da05fea54fdec2315b54a563d5b59f3b4e2b1e69c3a5841dda35019c01855cd",
|
"sha256:3da05fea54fdec2315b54a563d5b59f3b4e2b1e69c3a5841dda35019c01855cd",
|
||||||
@ -58,6 +112,22 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==8.4"
|
"version": "==8.4"
|
||||||
},
|
},
|
||||||
|
"django-celery-beat": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:97ae5eb309541551bdb07bf60cc57cadacf42a74287560ced2d2c06298620234",
|
||||||
|
"sha256:ab43049634fd18dc037927d7c2c7d5f67f95283a20ebbda55f42f8606412e66c"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==2.2.1"
|
||||||
|
},
|
||||||
|
"django-timezone-field": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:6dc782e31036a58da35b553bd00c70f112d794700025270d8a6a4c1d2e5b26c6",
|
||||||
|
"sha256:97780cde658daa5094ae515bb55ca97c1352928ab554041207ad515dee3fe971"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.5'",
|
||||||
|
"version": "==4.2.1"
|
||||||
|
},
|
||||||
"idna": {
|
"idna": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a",
|
"sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a",
|
||||||
@ -66,6 +136,36 @@
|
|||||||
"markers": "python_version >= '3'",
|
"markers": "python_version >= '3'",
|
||||||
"version": "==3.2"
|
"version": "==3.2"
|
||||||
},
|
},
|
||||||
|
"kombu": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:01481d99f4606f6939cdc9b637264ed353ee9e3e4f62cfb582324142c41a572d",
|
||||||
|
"sha256:e2dedd8a86c9077c350555153825a31e456a0dc20c15d5751f00137ec9c75f0a"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.6'",
|
||||||
|
"version": "==5.1.0"
|
||||||
|
},
|
||||||
|
"prompt-toolkit": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:08360ee3a3148bdb5163621709ee322ec34fc4375099afa4bbf751e9b7b7fa4f",
|
||||||
|
"sha256:7089d8d2938043508aa9420ec18ce0922885304cddae87fb96eebca942299f88"
|
||||||
|
],
|
||||||
|
"markers": "python_full_version >= '3.6.1'",
|
||||||
|
"version": "==3.0.19"
|
||||||
|
},
|
||||||
|
"python-crontab": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:4bbe7e720753a132ca4ca9d4094915f40e9d9dc8a807a4564007651018ce8c31"
|
||||||
|
],
|
||||||
|
"version": "==2.5.1"
|
||||||
|
},
|
||||||
|
"python-dateutil": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
|
||||||
|
"sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
|
"version": "==2.8.2"
|
||||||
|
},
|
||||||
"pytz": {
|
"pytz": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da",
|
"sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da",
|
||||||
@ -73,6 +173,13 @@
|
|||||||
],
|
],
|
||||||
"version": "==2021.1"
|
"version": "==2021.1"
|
||||||
},
|
},
|
||||||
|
"redis": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2",
|
||||||
|
"sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"
|
||||||
|
],
|
||||||
|
"version": "==3.5.3"
|
||||||
|
},
|
||||||
"requests": {
|
"requests": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
|
"sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
|
||||||
@ -81,6 +188,14 @@
|
|||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
|
||||||
"version": "==2.26.0"
|
"version": "==2.26.0"
|
||||||
},
|
},
|
||||||
|
"six": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
|
||||||
|
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
|
"version": "==1.16.0"
|
||||||
|
},
|
||||||
"sqlparse": {
|
"sqlparse": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0",
|
"sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0",
|
||||||
@ -96,6 +211,21 @@
|
|||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
|
||||||
"version": "==1.26.6"
|
"version": "==1.26.6"
|
||||||
|
},
|
||||||
|
"vine": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30",
|
||||||
|
"sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.6'",
|
||||||
|
"version": "==5.0.0"
|
||||||
|
},
|
||||||
|
"wcwidth": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784",
|
||||||
|
"sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"
|
||||||
|
],
|
||||||
|
"version": "==0.2.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"develop": {}
|
"develop": {}
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="panel">
|
<article>
|
||||||
<h1>{{ user.first_name }} {{ user.last_name }}</h1>
|
<h1>{{ user.first_name }} {{ user.last_name }}</h1>
|
||||||
<p>{{ user.email }}</p>
|
<section>
|
||||||
<p>
|
<p>{{ user.email }}</p>
|
||||||
<a href="{% url 'account-update' user.id %}">Update email/change password</a>
|
<p>
|
||||||
</p>
|
<a href="{% url 'account-update' user.id %}">Update email/change password</a>
|
||||||
</section>
|
</p>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -6,5 +6,63 @@
|
|||||||
<header>
|
<header>
|
||||||
<h1>Hi, {{user.first_name}}</h1>
|
<h1>Hi, {{user.first_name}}</h1>
|
||||||
</header>
|
</header>
|
||||||
|
<section>
|
||||||
|
<h2>Today, {% now "D, M j" %}</h2>
|
||||||
|
<div>
|
||||||
|
{% for event in today %}
|
||||||
|
<div class="today__event">
|
||||||
|
<strong class="today__date">{{event.date|date:"D, M j"}}</strong>
|
||||||
|
<span>{{event.name}}
|
||||||
|
{% if event.employee %} for
|
||||||
|
<a href="{% url 'employee-detail' event.employee.pk %}">{{event.employee}}</a>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
<span class="today__time">{{event.time|time:"TIME_FORMAT"}}</span>
|
||||||
|
<span><a href="{% url 'event-update' event.pk %}">Edit</a></span>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<p><a href="{% url 'event-create' %}" class="action-button">Add event</a></p>
|
||||||
|
<section>
|
||||||
|
<h3>Upcoming (next seven days)</h3>
|
||||||
|
<div>
|
||||||
|
{% for event in upcoming_events %}
|
||||||
|
<div class="today__event">
|
||||||
|
<strong class="today__date">{{event.date|date:"D, M j"}}</strong>
|
||||||
|
<span>{{event.name}}
|
||||||
|
{% if event.employee %} for
|
||||||
|
<a href="{% url 'employee-detail' event.employee.pk %}">{{event.employee}}</a>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
<span class="today__time">{{event.time|time:"TIME_FORMAT"}}</span>
|
||||||
|
<span><a href="{% url 'event-update' event.pk %}">Edit</a></span>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<p><a href="{% url 'event-list' %}">See all events</a></p>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h3>Recent Activity</h3>
|
||||||
|
{% regroup latest_activity by created_at.date as latest_activity_re %}
|
||||||
|
{% for date in latest_activity_re %}
|
||||||
|
<h5>{{date.grouper|date:"D, M j"}}</h5>
|
||||||
|
{% for entry in date.list %}
|
||||||
|
<p class="activity__item">
|
||||||
|
<span>{{entry.created_at|time:"TIME_FORMAT"}}</span>
|
||||||
|
<span>
|
||||||
|
{{entry.notes}}
|
||||||
|
<em>for</em>
|
||||||
|
<a href="{% url 'employee-detail' entry.employee.pk %}">{{entry.employee}}</a>
|
||||||
|
</span>
|
||||||
|
<span><a href="{% url 'entry-update' entry.employee.pk entry.pk %}">Edit</a></span>
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
</section>
|
||||||
</article>
|
</article>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import pytz
|
import pytz
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from datetime import date, timedelta
|
||||||
from django.shortcuts import render, reverse, redirect
|
from django.shortcuts import render, reverse, redirect
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.views.generic.base import TemplateView
|
from django.views.generic.base import TemplateView
|
||||||
@ -12,14 +13,25 @@ from django.contrib.auth.models import User
|
|||||||
from .models import Profile
|
from .models import Profile
|
||||||
from .forms import AccountUpdateForm, ProfileUpdateForm
|
from .forms import AccountUpdateForm, ProfileUpdateForm
|
||||||
|
|
||||||
|
from board.models import LogEntry, Event
|
||||||
|
|
||||||
|
|
||||||
class ProfileView(LoginRequiredMixin, TemplateView):
|
class ProfileView(LoginRequiredMixin, TemplateView):
|
||||||
template_name = 'accounts/profile.html'
|
template_name = 'accounts/profile.html'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
today = date.today()
|
||||||
|
tomorrow = today + timedelta(days=1)
|
||||||
|
enddate = today + timedelta(days=6)
|
||||||
context['profile'] = self.request.user.profile
|
context['profile'] = self.request.user.profile
|
||||||
|
context['latest_activity'] = LogEntry.objects.all()[:10]
|
||||||
|
context['today'] = Event.objects.filter(
|
||||||
|
date=today
|
||||||
|
)
|
||||||
|
context['upcoming_events'] = Event.objects.filter(
|
||||||
|
date__range=[tomorrow, enddate]
|
||||||
|
)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
class ProfileUpdateView(LoginRequiredMixin, UpdateView):
|
class ProfileUpdateView(LoginRequiredMixin, UpdateView):
|
||||||
|
|||||||
@ -4,8 +4,10 @@ from .models import (
|
|||||||
Employee,
|
Employee,
|
||||||
LogEntry,
|
LogEntry,
|
||||||
Todo,
|
Todo,
|
||||||
|
Event,
|
||||||
)
|
)
|
||||||
|
|
||||||
admin.site.register(Employee)
|
admin.site.register(Employee)
|
||||||
admin.site.register(LogEntry)
|
admin.site.register(LogEntry)
|
||||||
admin.site.register(Todo)
|
admin.site.register(Todo)
|
||||||
|
admin.site.register(Event)
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from django import forms
|
from django import forms
|
||||||
from .models import Employee, LogEntry, Todo
|
from django.utils import timezone
|
||||||
|
from .models import Employee, LogEntry, Todo, Event
|
||||||
from .regex import process_regex
|
from .regex import process_regex
|
||||||
|
|
||||||
class EmployeePreCreateForm(forms.Form):
|
class EmployeePreCreateForm(forms.Form):
|
||||||
@ -89,3 +90,25 @@ class TodoCreateForm(forms.ModelForm):
|
|||||||
'autofocus': 'autofocus'
|
'autofocus': 'autofocus'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EventForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Event
|
||||||
|
fields = (
|
||||||
|
'name',
|
||||||
|
'date',
|
||||||
|
'time',
|
||||||
|
'employee',
|
||||||
|
)
|
||||||
|
widgets = {
|
||||||
|
'name': forms.TextInput(attrs = {
|
||||||
|
'autofocus': 'autofocus'
|
||||||
|
}),
|
||||||
|
'date': forms.DateInput(attrs = {
|
||||||
|
'type': 'date',
|
||||||
|
'value': timezone.now().date(),
|
||||||
|
}),
|
||||||
|
'time': forms.DateInput(attrs = {
|
||||||
|
'type': 'time',
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|||||||
70
board/migrations/0003_auto_20210727_0006.py
Normal file
70
board/migrations/0003_auto_20210727_0006.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# Generated by Django 3.2.5 on 2021-07-27 00:06
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('board', '0002_auto_20210709_0103'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='employee',
|
||||||
|
options={'ordering': ('-hire_date',)},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='logentry',
|
||||||
|
options={'ordering': ('-created_at',)},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='employee',
|
||||||
|
name='department',
|
||||||
|
field=models.CharField(blank=True, max_length=64, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='employee',
|
||||||
|
name='first_name',
|
||||||
|
field=models.CharField(max_length=64),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='employee',
|
||||||
|
name='last_name',
|
||||||
|
field=models.CharField(max_length=64),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='employee',
|
||||||
|
name='manager',
|
||||||
|
field=models.CharField(blank=True, max_length=64, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='employee',
|
||||||
|
name='title',
|
||||||
|
field=models.CharField(blank=True, max_length=64, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='logentry',
|
||||||
|
name='notes',
|
||||||
|
field=models.CharField(max_length=500),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='todo',
|
||||||
|
name='description',
|
||||||
|
field=models.CharField(max_length=64),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Event',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('description', models.CharField(max_length=64)),
|
||||||
|
('date', models.DateField()),
|
||||||
|
('time', models.TimeField(blank=True, null=True)),
|
||||||
|
('employee', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='board.employee')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ('date', 'time'),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
18
board/migrations/0004_rename_description_event_name.py
Normal file
18
board/migrations/0004_rename_description_event_name.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.2.5 on 2021-07-27 01:53
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('board', '0003_auto_20210727_0006'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='event',
|
||||||
|
old_name='description',
|
||||||
|
new_name='name',
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -57,3 +57,19 @@ class Todo(models.Model):
|
|||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('todo-detail', kwargs={'pk': self.employee.pk, 'todo_pk': self.pk})
|
return reverse('todo-detail', kwargs={'pk': self.employee.pk, 'todo_pk': self.pk})
|
||||||
|
|
||||||
|
|
||||||
|
class Event(models.Model):
|
||||||
|
class Meta:
|
||||||
|
ordering = ('date', 'time')
|
||||||
|
|
||||||
|
name = models.CharField(max_length=64)
|
||||||
|
date = models.DateField()
|
||||||
|
time = models.TimeField(blank=True, null=True)
|
||||||
|
employee = models.ForeignKey(Employee, on_delete=models.CASCADE, blank=True, null=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.description} on {self.date} @ {self.time}"
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('event-detail', kwargs={'pk': self.pk})
|
||||||
|
|||||||
@ -31,6 +31,17 @@
|
|||||||
<p><strong>Initial comments</strong>:<p>
|
<p><strong>Initial comments</strong>:<p>
|
||||||
<blockquote>{{employee.initial_comments|linebreaksbr}}</blockquote>
|
<blockquote>{{employee.initial_comments|linebreaksbr}}</blockquote>
|
||||||
</section>
|
</section>
|
||||||
|
<section id="events">
|
||||||
|
<h3>Employee Events</h3>
|
||||||
|
{% for event in employee.event_set.all %}
|
||||||
|
<div class="today__event">
|
||||||
|
<strong class="today__date">{{event.date|date:"D, M j"}}</strong>
|
||||||
|
<span>{{event.name}}</span>
|
||||||
|
<span class="today__time">{{event.time|time:"TIME_FORMAT"}}</span>
|
||||||
|
<span><a href="{% url 'event-update' event.pk %}">Edit</a></span>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</section>
|
||||||
<section id="todos">
|
<section id="todos">
|
||||||
<h3>To-do's</h3>
|
<h3>To-do's</h3>
|
||||||
{% include "board/todo_list.html" with todo_list=employee.todo_set.all %}
|
{% include "board/todo_list.html" with todo_list=employee.todo_set.all %}
|
||||||
@ -45,11 +56,16 @@
|
|||||||
<p>
|
<p>
|
||||||
<a class="action-button" href="{% url 'entry-create' employee.pk %}">Add an Entry</a>
|
<a class="action-button" href="{% url 'entry-create' employee.pk %}">Add an Entry</a>
|
||||||
</p>
|
</p>
|
||||||
{% for entry in employee.logentry_set.all %}
|
{% regroup employee.logentry_set.all by created_at.date as activity %}
|
||||||
<p>
|
{% for date in activity %}
|
||||||
<span>{{entry.created_at|date:"SHORT_DATE_FORMAT"}}—</span>
|
<h5>{{date.grouper|date:"D, M j"}}</h5>
|
||||||
<span>{{entry.notes}}</span>
|
{% for entry in date.list %}
|
||||||
</p>
|
<p class="activity__item">
|
||||||
|
<span>{{entry.created_at|time:"TIME_FORMAT"}}</span>
|
||||||
|
<span>{{entry.notes}}</span>
|
||||||
|
<span><a href="{% url 'entry-update' employee.pk entry.pk %}">Edit</a></span>
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</section>
|
</section>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@ -8,7 +8,8 @@
|
|||||||
{% for employee in employee_list %}
|
{% for employee in employee_list %}
|
||||||
<p>
|
<p>
|
||||||
<a href="{% url 'employee-detail' employee.pk %}"><strong>{{employee.full_name}}</strong></a><br>
|
<a href="{% url 'employee-detail' employee.pk %}"><strong>{{employee.full_name}}</strong></a><br>
|
||||||
<small>Hire date: {{employee.hire_date|date:"SHORT_DATE_FORMAT"}}</small>
|
<small>Hire date: {{employee.hire_date|date:"SHORT_DATE_FORMAT"}}</small><br>
|
||||||
|
<small>Department: {{employee.department}}</small>
|
||||||
</p>
|
</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
13
board/templates/board/event_confirm_delete.html
Normal file
13
board/templates/board/event_confirm_delete.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<article>
|
||||||
|
<h1>Delete {{event}}</h1>
|
||||||
|
<form method="post" action="{% url 'event-delete' event.pk %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<p>
|
||||||
|
<input class="action-button action-delete" type="submit" value="Confirm Delete {{event}}"> or <a href="{% url 'event-detail' event.pk %}">cancel</a>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</article>
|
||||||
|
{% endblock %}
|
||||||
16
board/templates/board/event_create_form.html
Normal file
16
board/templates/board/event_create_form.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<article>
|
||||||
|
<h1>Create Event</h1>
|
||||||
|
<section>
|
||||||
|
<form method="POST" action="{% url 'event-create' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{form.as_p}}
|
||||||
|
<p>
|
||||||
|
<input class="action-button" type="submit" value="Create Event"> or <a href="{% url 'profile-detail' %}">cancel</a>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
{% endblock %}
|
||||||
17
board/templates/board/event_detail.html
Normal file
17
board/templates/board/event_detail.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<article>
|
||||||
|
<h1>Event</h1>
|
||||||
|
<section>
|
||||||
|
<div class="today__event">
|
||||||
|
<span>{{event.name}}
|
||||||
|
{% if event.employee %} for
|
||||||
|
<a href="{% url 'employee-detail' event.employee.pk %}">{{event.employee}}</a>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
<span class="today__time">{{event.time|time:"TIME_FORMAT"}}</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
{% endblock %}
|
||||||
16
board/templates/board/event_form.html
Normal file
16
board/templates/board/event_form.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<article>
|
||||||
|
<h1>Update Event</h1>
|
||||||
|
<section>
|
||||||
|
<form method="POST" action="{% url 'event-update' event.pk %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{form.as_p}}
|
||||||
|
<p>
|
||||||
|
<input class="action-button" type="submit" value="Save changes"> or <a href="{% url 'profile-detail' %}">cancel</a>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
{% endblock %}
|
||||||
41
board/templates/board/event_list.html
Normal file
41
board/templates/board/event_list.html
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<article>
|
||||||
|
<h1>Schedule</h1>
|
||||||
|
<p><a href="{% url 'event-create' %}" class="action-button">Add event</a></p>
|
||||||
|
<section>
|
||||||
|
{% for event in event_list %}
|
||||||
|
<div class="today__event">
|
||||||
|
<strong class="today__date">{{event.date|date:"D, M j"}}</strong>
|
||||||
|
<span>{{event.name}}
|
||||||
|
{% if event.employee %} for
|
||||||
|
<a href="{% url 'employee-detail' event.employee.pk %}">{{event.employee}}</a>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
<span class="today__time">{{event.time|time:"TIME_FORMAT"}}</span>
|
||||||
|
<span><a href="{% url 'event-update' event.pk %}">Edit</a></span>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<p>There are no events yet.</p>
|
||||||
|
{% endfor %}
|
||||||
|
</section>
|
||||||
|
<section class="pagination">
|
||||||
|
<span class="step-links">
|
||||||
|
{% if page_obj.has_previous %}
|
||||||
|
<a href="?page=1">« first</a>
|
||||||
|
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<span class="current">
|
||||||
|
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{% if page_obj.has_next %}
|
||||||
|
<a href="?page={{ page_obj.next_page_number }}">next</a>
|
||||||
|
<a href="?page={{ page_obj.paginator.num_pages }}">last »</a>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
{% endblock %}
|
||||||
@ -3,10 +3,10 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<article>
|
<article>
|
||||||
<h1>Delete {{logentry}}</h1>
|
<h1>Delete {{logentry}}</h1>
|
||||||
<form method="post" action="{% url 'logentry-delete' logentry.pk %}">
|
<form method="post" action="{% url 'entry-delete' employee.pk logentry.pk %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<p>
|
<p>
|
||||||
<input class="action-button action-delete" type="submit" value="Confirm Delete {{logentry}}"> or <a href="{% url 'logentry-detail' logentry.pk %}">cancel</a>
|
<input class="action-button action-button--danger" type="submit" value="Confirm Delete"> or <a href="{% url 'employee-detail' employee.pk %}">cancel</a>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@ -2,9 +2,18 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<article>
|
<article>
|
||||||
<h1></h1>
|
<header>
|
||||||
<section>
|
<h1>Update Event</h1>
|
||||||
|
<p><a class="action-button action-button--danger" href="{% url 'entry-delete' employee.pk logentry.pk %}">Delete this entry</a></p>
|
||||||
</section>
|
</header>
|
||||||
|
<section>
|
||||||
|
<form method="POST" action="{% url 'entry-update' employee.pk logentry.pk %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{form.as_p}}
|
||||||
|
<p>
|
||||||
|
<input class="action-button" type="submit" value="Save changes"> or <a href="{% url 'employee-detail' employee.pk %}">cancel</a>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
</article>
|
</article>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -32,4 +32,12 @@ urlpatterns = [
|
|||||||
])),
|
])),
|
||||||
|
|
||||||
])),
|
])),
|
||||||
|
|
||||||
|
path('events/', views.EventListView.as_view(), name='event-list'),
|
||||||
|
path('events/new/', views.EventCreateView.as_view(), name='event-create'),
|
||||||
|
path('events/<int:pk>/', include([
|
||||||
|
path('', views.EventDetailView.as_view(), name='event-detail'),
|
||||||
|
path('update/', views.EventUpdateView.as_view(), name='event-update'),
|
||||||
|
path('delete/', views.EventDeleteView.as_view(), name='event-delete'),
|
||||||
|
])),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -7,14 +7,15 @@ from django.views.generic.list import ListView
|
|||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from .models import Employee, LogEntry, Todo
|
from .models import Employee, LogEntry, Todo, Event
|
||||||
from .forms import (
|
from .forms import (
|
||||||
EmployeeForm,
|
EmployeeForm,
|
||||||
EmployeePreCreateForm,
|
EmployeePreCreateForm,
|
||||||
EmployeeArchiveForm,
|
EmployeeArchiveForm,
|
||||||
LogEntryForm,
|
LogEntryForm,
|
||||||
TodoForm,
|
TodoForm,
|
||||||
TodoCreateForm
|
TodoCreateForm,
|
||||||
|
EventForm,
|
||||||
)
|
)
|
||||||
|
|
||||||
class SearchResultsView(ListView):
|
class SearchResultsView(ListView):
|
||||||
@ -117,7 +118,14 @@ class LogEntryUpdateView(LoginRequiredMixin, UpdateView):
|
|||||||
class LogEntryDeleteView(LoginRequiredMixin, DeleteView):
|
class LogEntryDeleteView(LoginRequiredMixin, DeleteView):
|
||||||
model = LogEntry
|
model = LogEntry
|
||||||
pk_url_kwarg = 'entry_pk'
|
pk_url_kwarg = 'entry_pk'
|
||||||
success_url = reverse_lazy('entry-list')
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context['employee'] = Employee.objects.get(pk=self.kwargs['pk'])
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse('employee-detail', kwargs={'pk': self.kwargs['pk']})
|
||||||
|
|
||||||
|
|
||||||
# Todos
|
# Todos
|
||||||
@ -171,3 +179,27 @@ class TodoDeleteView(LoginRequiredMixin, DeleteView):
|
|||||||
|
|
||||||
class TodoDeleteDoneView(LoginRequiredMixin, TemplateView):
|
class TodoDeleteDoneView(LoginRequiredMixin, TemplateView):
|
||||||
template_name = 'board/item_deleted.html'
|
template_name = 'board/item_deleted.html'
|
||||||
|
|
||||||
|
|
||||||
|
# Events
|
||||||
|
class EventListView(LoginRequiredMixin, ListView):
|
||||||
|
model = Event
|
||||||
|
paginate_by = 100
|
||||||
|
ordering = ('-date', '-time')
|
||||||
|
|
||||||
|
class EventCreateView(LoginRequiredMixin, CreateView):
|
||||||
|
model = Event
|
||||||
|
template_name_suffix = '_create_form'
|
||||||
|
form_class = EventForm
|
||||||
|
success_url = reverse_lazy('profile-detail')
|
||||||
|
|
||||||
|
class EventDetailView(LoginRequiredMixin, DetailView):
|
||||||
|
model = Event
|
||||||
|
|
||||||
|
class EventUpdateView(LoginRequiredMixin, UpdateView):
|
||||||
|
model = Event
|
||||||
|
form_class = EventForm
|
||||||
|
|
||||||
|
class EventDeleteView(LoginRequiredMixin, DeleteView):
|
||||||
|
model = Event
|
||||||
|
success_url = reverse_lazy('profile-detail')
|
||||||
|
|||||||
@ -0,0 +1,5 @@
|
|||||||
|
# This will make sure the app is always imported when
|
||||||
|
# Django starts so that shared_task will use this app.
|
||||||
|
from .celery import app as celery_app
|
||||||
|
|
||||||
|
__all__ = ('celery_app',)
|
||||||
50
onboard/celery.py
Normal file
50
onboard/celery.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from celery import Celery
|
||||||
|
|
||||||
|
# Set the default Django settings module for the 'celery' program.
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'onboard.settings_dev')
|
||||||
|
|
||||||
|
app = Celery('onboard')
|
||||||
|
|
||||||
|
# Using a string here means the worker doesn't have to serialize
|
||||||
|
# the configuration object to child processes.
|
||||||
|
# - namespace='CELERY' means all celery-related configuration keys
|
||||||
|
# should have a `CELERY_` prefix.
|
||||||
|
app.config_from_object('django.conf:settings', namespace='CELERY')
|
||||||
|
|
||||||
|
# Load task modules from all registered Django apps.
|
||||||
|
app.autodiscover_tasks()
|
||||||
|
|
||||||
|
|
||||||
|
@app.task(bind=True)
|
||||||
|
def debug_task(self):
|
||||||
|
print(f'Request: {self.request!r}')
|
||||||
|
|
||||||
|
# app.conf.beat_schedule = {
|
||||||
|
# #Scheduler Name
|
||||||
|
# 'print-message-ten-seconds': {
|
||||||
|
# # Task Name (Name Specified in Decorator)
|
||||||
|
# 'task': 'print_msg_main',
|
||||||
|
# # Schedule
|
||||||
|
# 'schedule': 10.0,
|
||||||
|
# # Function Arguments
|
||||||
|
# 'args': ("Hello",)
|
||||||
|
# },
|
||||||
|
# #Scheduler Name
|
||||||
|
# 'print-time-twenty-seconds': {
|
||||||
|
# # Task Name (Name Specified in Decorator)
|
||||||
|
# 'task': 'print_time',
|
||||||
|
# # Schedule
|
||||||
|
# 'schedule': 20.0,
|
||||||
|
# },
|
||||||
|
# #Scheduler Name
|
||||||
|
# 'calculate-forty-seconds': {
|
||||||
|
# # Task Name (Name Specified in Decorator)
|
||||||
|
# 'task': 'get_calculation',
|
||||||
|
# # Schedule
|
||||||
|
# 'schedule': 40.0,
|
||||||
|
# # Function Arguments
|
||||||
|
# 'args': (10,20)
|
||||||
|
# },
|
||||||
|
# }
|
||||||
17
onboard/middleware.py
Normal file
17
onboard/middleware.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import pytz
|
||||||
|
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
class TimezoneMiddleware:
|
||||||
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
tzname = request.session.get('timezone')
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
tzname = request.user.profile.timezone
|
||||||
|
if tzname:
|
||||||
|
timezone.activate(pytz.timezone(tzname))
|
||||||
|
else:
|
||||||
|
timezone.deactivate()
|
||||||
|
return self.get_response(request)
|
||||||
@ -25,7 +25,7 @@ with open('/etc/secret_key.txt') as f:
|
|||||||
SECRET_KEY = f.read().strip()
|
SECRET_KEY = f.read().strip()
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = os.environ.get('DJANGO_DEBUG', '') != 'False'
|
DEBUG = False
|
||||||
|
|
||||||
ALLOWED_HOSTS = ['onboard.windmillapps.org', '127.0.0.1']
|
ALLOWED_HOSTS = ['onboard.windmillapps.org', '127.0.0.1']
|
||||||
|
|
||||||
@ -126,10 +126,11 @@ USE_TZ = True
|
|||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/3.2/howto/static-files/
|
# https://docs.djangoproject.com/en/3.2/howto/static-files/
|
||||||
|
STATICFILES_DIRS = [
|
||||||
|
BASE_DIR / 'static'
|
||||||
|
]
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
STATIC_ROOT = [BASE_DIR / 'static/']
|
STATIC_ROOT = '/var/www/onboard.windmillapps.org/static/'
|
||||||
STATICFILES_DIRS = [BASE_DIR / 'static']
|
|
||||||
|
|
||||||
|
|
||||||
# Default primary key field type
|
# Default primary key field type
|
||||||
@ -162,7 +163,7 @@ LOGGING = {
|
|||||||
|
|
||||||
# Email
|
# Email
|
||||||
ANYMAIL = {
|
ANYMAIL = {
|
||||||
"MAILGUN_API_KEY": os.environ.get('MAILGUN_API_KEY'),
|
"MAILGUN_API_KEY": "key-b65d1f9e486825a7d01d099fd3062c2b",
|
||||||
}
|
}
|
||||||
|
|
||||||
SERVER_EMAIL = 'noreply@onboard.windmillapps.org'
|
SERVER_EMAIL = 'noreply@onboard.windmillapps.org'
|
||||||
@ -173,10 +174,8 @@ ADMINS = (
|
|||||||
)
|
)
|
||||||
MANAGERS = ADMINS
|
MANAGERS = ADMINS
|
||||||
|
|
||||||
EMAIL_BACKEND = os.environ.get('EMAIL_BACKEND')
|
EMAIL_BACKEND = 'anymail.backends.mailgun.EmailBackend'
|
||||||
|
SECURE_HSTS_SECONDS = True
|
||||||
|
SECURE_SSL_REDIRECT = True
|
||||||
SECURE_HSTS_SECONDS = os.environ.get('DJANGO_SECURE_HSTS_SECONDS', '') != 'False'
|
SESSION_COOKIE_SECURE = True
|
||||||
SECURE_SSL_REDIRECT = os.environ.get('DJANGO_SECURE_SSL_REDIRECT', '') != 'False'
|
CSRF_COOKIE_SECURE = True
|
||||||
SESSION_COOKIE_SECURE = os.environ.get('DJANGO_SESSION_COOKIE_SECURE', '') != 'False'
|
|
||||||
CSRF_COOKIE_SECURE = os.environ.get('DJANGO_CSRF_COOKIE_SECURE', '') != 'False'
|
|
||||||
|
|||||||
145
onboard/settings_dev.py
Normal file
145
onboard/settings_dev.py
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
"""
|
||||||
|
Django settings for onboard project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 3.2.5.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.2/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/3.2/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = 'django-insecure-fdq1(f%l*__obb*zvb3gqnlki!ryr@_1_5om(_2ju3mu70mi4%'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'xcarbon.lan']
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'django.contrib.sites',
|
||||||
|
'django.contrib.flatpages',
|
||||||
|
'django_celery_beat',
|
||||||
|
'accounts.apps.AccountsConfig',
|
||||||
|
'board.apps.BoardConfig',
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
'onboard.middleware.TimezoneMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'onboard.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [BASE_DIR / 'templates'],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'onboard.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': BASE_DIR / 'db.sqlite3',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/3.2/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/3.2/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
STATICFILES_DIRS = [BASE_DIR / 'static']
|
||||||
|
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
# Site ID
|
||||||
|
SITE_ID = 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Celery - prefix with CELERY_
|
||||||
|
CELERY_BROKER_URL = "redis://localhost:6379"
|
||||||
|
CELERY_RESULT_BACKEND = CELERY_BROKER_URL
|
||||||
|
CELERY_ACCEPT_CONTENT = ["json"]
|
||||||
|
CELERY_TASK_SERIALIZER = "json"
|
||||||
|
CELERY_TASK_TRACK_STARTED = True
|
||||||
@ -1,8 +1,9 @@
|
|||||||
:root {
|
:root {
|
||||||
--white: #fdfdff;
|
--white: #f8f8fb;
|
||||||
--black: #393d3f;
|
--black: #393d3f;
|
||||||
--grey: #a7b4bb;
|
--grey: #a7b4bb;
|
||||||
--blue: #2288a2;
|
--blue: #10638c;
|
||||||
|
--red: #8c1016;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
@ -63,7 +64,7 @@ blockquote {
|
|||||||
border-left: 2px solid var(--blue);
|
border-left: 2px solid var(--blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
body > header {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,11 +96,14 @@ article {
|
|||||||
|
|
||||||
.navbar__mobile {
|
.navbar__mobile {
|
||||||
display: none;
|
display: none;
|
||||||
|
font-weight: 900;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar__menu {
|
.navbar__mobile .menu__items {
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.navbar__menu_title {
|
.navbar__menu_title {
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
padding: 0.2rem 1rem;
|
padding: 0.2rem 1rem;
|
||||||
@ -199,10 +203,16 @@ article {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.action-button--danger {
|
||||||
|
background-color: var(--red);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* FORMS */
|
/* FORMS */
|
||||||
input[type=text]:not([name=description]),
|
input[type=text]:not([name=description]),
|
||||||
input[type=number],
|
input[type=number],
|
||||||
|
input[type=date],
|
||||||
|
input[type=time],
|
||||||
input[type=password],
|
input[type=password],
|
||||||
input[type=search],
|
input[type=search],
|
||||||
textarea, select {
|
textarea, select {
|
||||||
@ -223,10 +233,6 @@ input[name=description] {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
input:focus, textarea:focus, select:focus {
|
|
||||||
border-color: var(--blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=radio], input[type=checkbox] {
|
input[type=radio], input[type=checkbox] {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 1.5rem;
|
width: 1.5rem;
|
||||||
@ -256,6 +262,10 @@ li label {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input:focus, textarea:focus, select:focus {
|
||||||
|
border-color: var(--blue) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -301,3 +311,35 @@ hgroup {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.log_entry {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 2fr 1fr;
|
||||||
|
column-gap: 0.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log_entry--employee {
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.today__event {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 2fr 0.5fr 0.5fr;
|
||||||
|
column-gap: 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
border-bottom: 0.046rem solid var(--grey);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.activity__item {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 0.25fr 2fr 0.25fr;
|
||||||
|
column-gap: 0.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
@ -26,6 +26,7 @@
|
|||||||
<input name="q" type="search" placeholder="Search...">
|
<input name="q" type="search" placeholder="Search...">
|
||||||
</form>
|
</form>
|
||||||
<div class="nav__employees">
|
<div class="nav__employees">
|
||||||
|
<a href="{% url 'profile-detail' %}">Today</a>
|
||||||
<a href="{% url 'employee-list' %}">Employees</a>
|
<a href="{% url 'employee-list' %}">Employees</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav__auth">
|
<div class="nav__auth">
|
||||||
@ -37,12 +38,13 @@
|
|||||||
<a class="action-button" href="{% url 'logout' %}">Logout</a>
|
<a class="action-button" href="{% url 'logout' %}">Logout</a>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
<span>OnBoard</span>
|
||||||
<a class="nav__login" class="action-button" href="{% url 'login' %}">Login</a>
|
<a class="nav__login" class="action-button" href="{% url 'login' %}">Login</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</nav>
|
</nav>
|
||||||
<nav class="navbar__mobile">
|
<nav class="navbar__mobile">
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<details class="menu_">
|
<details class="menu">
|
||||||
<summary class="menu__title">Menu</summary>
|
<summary class="menu__title">Menu</summary>
|
||||||
<dl class="menu__items">
|
<dl class="menu__items">
|
||||||
<dt>
|
<dt>
|
||||||
@ -51,12 +53,15 @@
|
|||||||
</form>
|
</form>
|
||||||
</dt>
|
</dt>
|
||||||
<br>
|
<br>
|
||||||
|
<dt><a href="{% url 'profile-detail' %}">Today</a></dt>
|
||||||
|
<br>
|
||||||
<dt><a href="{% url 'employee-list' %}">Employees</a></dt>
|
<dt><a href="{% url 'employee-list' %}">Employees</a></dt>
|
||||||
<br>
|
<br>
|
||||||
<dt><a class="action-button" href="{% url 'logout' %}">Logout</a></dt>
|
<dt><a class="action-button" href="{% url 'logout' %}">Logout</a></dt>
|
||||||
</dl>
|
</dl>
|
||||||
</details>
|
</details>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
<span>OnBoard</span>
|
||||||
<a class="nav__login" class="action-button" href="{% url 'login' %}">Login</a>
|
<a class="nav__login" class="action-button" href="{% url 'login' %}">Login</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user