diff --git a/Pipfile b/Pipfile
index 8a5a5a0..48ec8ac 100644
--- a/Pipfile
+++ b/Pipfile
@@ -6,6 +6,8 @@ name = "pypi"
[packages]
django = "*"
django-anymail = {extras = ["mailgun"], version = "*"}
+celery = {extras = ["redis"], version = "*"}
+django-celery-beat = "*"
[dev-packages]
diff --git a/Pipfile.lock b/Pipfile.lock
index 88bca9a..40545b0 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "88b6a9fdda6471bfc16c42a906945bb034d4b2d576f31179a72471296eac58cd"
+ "sha256": "ffe96f34ca738a35a283e9ac98b5f1a74609c67619f18e8eb7ba62967247766f"
},
"pipfile-spec": 6,
"requires": {
@@ -16,6 +16,14 @@
]
},
"default": {
+ "amqp": {
+ "hashes": [
+ "sha256:03e16e94f2b34c31f8bf1206d8ddd3ccaa4c315f7f6a1879b7b1210d229568c2",
+ "sha256:493a2ac6788ce270a2f6a765b017299f60c1998f5a8617908ee9be082f7300fb"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==5.0.6"
+ },
"asgiref": {
"hashes": [
"sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9",
@@ -24,6 +32,24 @@
"markers": "python_version >= '3.6'",
"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": {
"hashes": [
"sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee",
@@ -39,6 +65,34 @@
"markers": "python_version >= '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": {
"hashes": [
"sha256:3da05fea54fdec2315b54a563d5b59f3b4e2b1e69c3a5841dda35019c01855cd",
@@ -58,6 +112,22 @@
"index": "pypi",
"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": {
"hashes": [
"sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a",
@@ -66,6 +136,36 @@
"markers": "python_version >= '3'",
"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": {
"hashes": [
"sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da",
@@ -73,6 +173,13 @@
],
"version": "==2021.1"
},
+ "redis": {
+ "hashes": [
+ "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2",
+ "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"
+ ],
+ "version": "==3.5.3"
+ },
"requests": {
"hashes": [
"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'",
"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": {
"hashes": [
"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'",
"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": {}
diff --git a/accounts/templates/accounts/account_detail.html b/accounts/templates/accounts/account_detail.html
index 4e6c6e3..0e277e6 100755
--- a/accounts/templates/accounts/account_detail.html
+++ b/accounts/templates/accounts/account_detail.html
@@ -1,11 +1,13 @@
{% extends 'base.html' %}
{% block content %}
-
+
+
{% endblock %}
diff --git a/accounts/templates/accounts/profile.html b/accounts/templates/accounts/profile.html
index 927dec5..9a54ced 100644
--- a/accounts/templates/accounts/profile.html
+++ b/accounts/templates/accounts/profile.html
@@ -6,5 +6,63 @@
+
+ Today, {% now "D, M j" %}
+
+ {% for event in today %}
+
+
{{event.date|date:"D, M j"}}
+
{{event.name}}
+ {% if event.employee %} for
+ {{event.employee}}
+ {% endif %}
+
+
{{event.time|time:"TIME_FORMAT"}}
+
Edit
+
+ {% endfor %}
+
+
+
Add event
+
+ Upcoming (next seven days)
+
+ {% for event in upcoming_events %}
+
+
{{event.date|date:"D, M j"}}
+
{{event.name}}
+ {% if event.employee %} for
+ {{event.employee}}
+ {% endif %}
+
+
{{event.time|time:"TIME_FORMAT"}}
+
Edit
+
+ {% endfor %}
+
+ See all events
+
+
+ Recent Activity
+ {% regroup latest_activity by created_at.date as latest_activity_re %}
+ {% for date in latest_activity_re %}
+ {{date.grouper|date:"D, M j"}}
+ {% for entry in date.list %}
+
+ {{entry.created_at|time:"TIME_FORMAT"}}
+
+ {{entry.notes}}
+ for
+ {{entry.employee}}
+
+ Edit
+
+ {% endfor %}
+ {% endfor %}
+
{% endblock %}
+
+
+
+
diff --git a/accounts/views.py b/accounts/views.py
index b13a258..1d0ffad 100755
--- a/accounts/views.py
+++ b/accounts/views.py
@@ -1,5 +1,6 @@
import pytz
from django.utils import timezone
+from datetime import date, timedelta
from django.shortcuts import render, reverse, redirect
from django.urls import reverse_lazy
from django.views.generic.base import TemplateView
@@ -12,14 +13,25 @@ from django.contrib.auth.models import User
from .models import Profile
from .forms import AccountUpdateForm, ProfileUpdateForm
+from board.models import LogEntry, Event
+
class ProfileView(LoginRequiredMixin, TemplateView):
template_name = 'accounts/profile.html'
def get_context_data(self, **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['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
class ProfileUpdateView(LoginRequiredMixin, UpdateView):
diff --git a/board/admin.py b/board/admin.py
index fa58b84..2bbcd67 100644
--- a/board/admin.py
+++ b/board/admin.py
@@ -4,8 +4,10 @@ from .models import (
Employee,
LogEntry,
Todo,
+ Event,
)
admin.site.register(Employee)
admin.site.register(LogEntry)
admin.site.register(Todo)
+admin.site.register(Event)
diff --git a/board/forms.py b/board/forms.py
index d32ebe2..0305e3c 100644
--- a/board/forms.py
+++ b/board/forms.py
@@ -1,6 +1,7 @@
from datetime import datetime
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
class EmployeePreCreateForm(forms.Form):
@@ -89,3 +90,25 @@ class TodoCreateForm(forms.ModelForm):
'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',
+ }),
+ }
diff --git a/board/migrations/0003_auto_20210727_0006.py b/board/migrations/0003_auto_20210727_0006.py
new file mode 100644
index 0000000..07ee0d7
--- /dev/null
+++ b/board/migrations/0003_auto_20210727_0006.py
@@ -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'),
+ },
+ ),
+ ]
diff --git a/board/migrations/0004_rename_description_event_name.py b/board/migrations/0004_rename_description_event_name.py
new file mode 100644
index 0000000..dc8a6d3
--- /dev/null
+++ b/board/migrations/0004_rename_description_event_name.py
@@ -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',
+ ),
+ ]
diff --git a/board/models.py b/board/models.py
index 1d3ac53..b56eeba 100644
--- a/board/models.py
+++ b/board/models.py
@@ -57,3 +57,19 @@ class Todo(models.Model):
def get_absolute_url(self):
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})
diff --git a/board/templates/board/employee_detail.html b/board/templates/board/employee_detail.html
index 4874dd1..4f57e28 100644
--- a/board/templates/board/employee_detail.html
+++ b/board/templates/board/employee_detail.html
@@ -31,6 +31,17 @@
Initial comments:
{{employee.initial_comments|linebreaksbr}}
+
+ Employee Events
+ {% for event in employee.event_set.all %}
+
+
{{event.date|date:"D, M j"}}
+
{{event.name}}
+
{{event.time|time:"TIME_FORMAT"}}
+
Edit
+
+ {% endfor %}
+
To-do's
{% include "board/todo_list.html" with todo_list=employee.todo_set.all %}
@@ -45,11 +56,16 @@
Add an Entry
- {% for entry in employee.logentry_set.all %}
-
- {{entry.created_at|date:"SHORT_DATE_FORMAT"}}—
- {{entry.notes}}
-
+ {% regroup employee.logentry_set.all by created_at.date as activity %}
+ {% for date in activity %}
+ {{date.grouper|date:"D, M j"}}
+ {% for entry in date.list %}
+
+ {{entry.created_at|time:"TIME_FORMAT"}}
+ {{entry.notes}}
+ Edit
+
+ {% endfor %}
{% endfor %}
diff --git a/board/templates/board/employee_list.html b/board/templates/board/employee_list.html
index ac68ece..13f37a0 100644
--- a/board/templates/board/employee_list.html
+++ b/board/templates/board/employee_list.html
@@ -8,7 +8,8 @@
{% for employee in employee_list %}
{{employee.full_name}}
- Hire date: {{employee.hire_date|date:"SHORT_DATE_FORMAT"}}
+ Hire date: {{employee.hire_date|date:"SHORT_DATE_FORMAT"}}
+ Department: {{employee.department}}
{% endfor %}
diff --git a/board/templates/board/event_confirm_delete.html b/board/templates/board/event_confirm_delete.html
new file mode 100644
index 0000000..831a651
--- /dev/null
+++ b/board/templates/board/event_confirm_delete.html
@@ -0,0 +1,13 @@
+{% extends "base.html" %}
+
+{% block content %}
+
+ Delete {{event}}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/board/templates/board/event_create_form.html b/board/templates/board/event_create_form.html
new file mode 100644
index 0000000..aaa0dfc
--- /dev/null
+++ b/board/templates/board/event_create_form.html
@@ -0,0 +1,16 @@
+{% extends "base.html" %}
+
+{% block content %}
+
+ Create Event
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/board/templates/board/event_detail.html b/board/templates/board/event_detail.html
new file mode 100644
index 0000000..c1934b8
--- /dev/null
+++ b/board/templates/board/event_detail.html
@@ -0,0 +1,17 @@
+{% extends "base.html" %}
+
+{% block content %}
+
+ Event
+
+
+
{{event.name}}
+ {% if event.employee %} for
+ {{event.employee}}
+ {% endif %}
+
+
{{event.time|time:"TIME_FORMAT"}}
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/board/templates/board/event_form.html b/board/templates/board/event_form.html
new file mode 100644
index 0000000..4cd0c9c
--- /dev/null
+++ b/board/templates/board/event_form.html
@@ -0,0 +1,16 @@
+{% extends "base.html" %}
+
+{% block content %}
+
+ Update Event
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/board/templates/board/event_list.html b/board/templates/board/event_list.html
new file mode 100644
index 0000000..50110fc
--- /dev/null
+++ b/board/templates/board/event_list.html
@@ -0,0 +1,41 @@
+{% extends "base.html" %}
+
+{% block content %}
+
+ Schedule
+ Add event
+
+ {% for event in event_list %}
+
+
{{event.date|date:"D, M j"}}
+
{{event.name}}
+ {% if event.employee %} for
+ {{event.employee}}
+ {% endif %}
+
+
{{event.time|time:"TIME_FORMAT"}}
+
Edit
+
+ {% empty %}
+ There are no events yet.
+ {% endfor %}
+
+
+
+{% endblock %}
diff --git a/board/templates/board/logentry_confirm_delete.html b/board/templates/board/logentry_confirm_delete.html
index 7aae8ca..8eb1b92 100644
--- a/board/templates/board/logentry_confirm_delete.html
+++ b/board/templates/board/logentry_confirm_delete.html
@@ -3,10 +3,10 @@
{% block content %}
Delete {{logentry}}
-
diff --git a/board/templates/board/logentry_form.html b/board/templates/board/logentry_form.html
index b35e99a..0708d6a 100644
--- a/board/templates/board/logentry_form.html
+++ b/board/templates/board/logentry_form.html
@@ -2,9 +2,18 @@
{% block content %}
-
-
-
+
+
+
{% endblock %}
\ No newline at end of file
diff --git a/board/urls.py b/board/urls.py
index 0da3ec1..0ac94c6 100644
--- a/board/urls.py
+++ b/board/urls.py
@@ -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//', 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'),
+ ])),
]
diff --git a/board/views.py b/board/views.py
index d0be241..dc4ccce 100644
--- a/board/views.py
+++ b/board/views.py
@@ -7,14 +7,15 @@ from django.views.generic.list import ListView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Q
-from .models import Employee, LogEntry, Todo
+from .models import Employee, LogEntry, Todo, Event
from .forms import (
EmployeeForm,
EmployeePreCreateForm,
EmployeeArchiveForm,
LogEntryForm,
TodoForm,
- TodoCreateForm
+ TodoCreateForm,
+ EventForm,
)
class SearchResultsView(ListView):
@@ -117,7 +118,14 @@ class LogEntryUpdateView(LoginRequiredMixin, UpdateView):
class LogEntryDeleteView(LoginRequiredMixin, DeleteView):
model = LogEntry
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
@@ -170,4 +178,28 @@ class TodoDeleteView(LoginRequiredMixin, DeleteView):
return reverse('todo-deleted', kwargs={'pk': self.kwargs['pk']})
class TodoDeleteDoneView(LoginRequiredMixin, TemplateView):
- template_name = 'board/item_deleted.html'
\ No newline at end of file
+ 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')
diff --git a/onboard/__init__.py b/onboard/__init__.py
index e69de29..1e3599b 100644
--- a/onboard/__init__.py
+++ b/onboard/__init__.py
@@ -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',)
\ No newline at end of file
diff --git a/onboard/celery.py b/onboard/celery.py
new file mode 100644
index 0000000..0fa169f
--- /dev/null
+++ b/onboard/celery.py
@@ -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)
+# },
+# }
\ No newline at end of file
diff --git a/onboard/middleware.py b/onboard/middleware.py
new file mode 100644
index 0000000..8260705
--- /dev/null
+++ b/onboard/middleware.py
@@ -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)
diff --git a/onboard/settings.py b/onboard/settings.py
index 6d2e0bb..a0775c0 100644
--- a/onboard/settings.py
+++ b/onboard/settings.py
@@ -25,7 +25,7 @@ with open('/etc/secret_key.txt') as f:
SECRET_KEY = f.read().strip()
# 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']
@@ -126,10 +126,11 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/
-
+STATICFILES_DIRS = [
+ BASE_DIR / 'static'
+ ]
STATIC_URL = '/static/'
-STATIC_ROOT = [BASE_DIR / 'static/']
-STATICFILES_DIRS = [BASE_DIR / 'static']
+STATIC_ROOT = '/var/www/onboard.windmillapps.org/static/'
# Default primary key field type
@@ -162,7 +163,7 @@ LOGGING = {
# Email
ANYMAIL = {
- "MAILGUN_API_KEY": os.environ.get('MAILGUN_API_KEY'),
+ "MAILGUN_API_KEY": "key-b65d1f9e486825a7d01d099fd3062c2b",
}
SERVER_EMAIL = 'noreply@onboard.windmillapps.org'
@@ -173,10 +174,8 @@ ADMINS = (
)
MANAGERS = ADMINS
-EMAIL_BACKEND = os.environ.get('EMAIL_BACKEND')
-
-
-SECURE_HSTS_SECONDS = os.environ.get('DJANGO_SECURE_HSTS_SECONDS', '') != 'False'
-SECURE_SSL_REDIRECT = os.environ.get('DJANGO_SECURE_SSL_REDIRECT', '') != 'False'
-SESSION_COOKIE_SECURE = os.environ.get('DJANGO_SESSION_COOKIE_SECURE', '') != 'False'
-CSRF_COOKIE_SECURE = os.environ.get('DJANGO_CSRF_COOKIE_SECURE', '') != 'False'
\ No newline at end of file
+EMAIL_BACKEND = 'anymail.backends.mailgun.EmailBackend'
+SECURE_HSTS_SECONDS = True
+SECURE_SSL_REDIRECT = True
+SESSION_COOKIE_SECURE = True
+CSRF_COOKIE_SECURE = True
diff --git a/onboard/settings_dev.py b/onboard/settings_dev.py
new file mode 100644
index 0000000..1f84911
--- /dev/null
+++ b/onboard/settings_dev.py
@@ -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
diff --git a/static/styles/main.css b/static/styles/main.css
index 07c66e4..0e037c5 100644
--- a/static/styles/main.css
+++ b/static/styles/main.css
@@ -1,8 +1,9 @@
:root {
- --white: #fdfdff;
+ --white: #f8f8fb;
--black: #393d3f;
--grey: #a7b4bb;
- --blue: #2288a2;
+ --blue: #10638c;
+ --red: #8c1016;
}
html {
@@ -63,7 +64,7 @@ blockquote {
border-left: 2px solid var(--blue);
}
-header {
+body > header {
padding: 1rem;
}
@@ -95,11 +96,14 @@ article {
.navbar__mobile {
display: none;
+ font-weight: 900;
}
-.navbar__menu {
-
+.navbar__mobile .menu__items {
+ text-align: center;
}
+
+
.navbar__menu_title {
font-weight: 900;
padding: 0.2rem 1rem;
@@ -199,10 +203,16 @@ article {
text-decoration: none;
}
+.action-button--danger {
+ background-color: var(--red);
+}
+
/* FORMS */
input[type=text]:not([name=description]),
input[type=number],
+input[type=date],
+input[type=time],
input[type=password],
input[type=search],
textarea, select {
@@ -223,10 +233,6 @@ input[name=description] {
box-sizing: border-box;
}
-input:focus, textarea:focus, select:focus {
- border-color: var(--blue);
-}
-
input[type=radio], input[type=checkbox] {
cursor: pointer;
width: 1.5rem;
@@ -256,6 +262,10 @@ li label {
display: inline-block;
}
+input:focus, textarea:focus, select:focus {
+ border-color: var(--blue) !important;
+}
+
@@ -300,4 +310,36 @@ hgroup {
right: 0;
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;
}
\ No newline at end of file
diff --git a/templates/base.html b/templates/base.html
index 61f2fc5..9ec42f9 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -26,6 +26,7 @@
{% else %}
+ OnBoard
Login
{% endif %}