Add basic events

This commit is contained in:
Nathan Chapman 2021-07-27 09:48:58 -06:00
parent 608498b296
commit fa98be63fa
21 changed files with 405 additions and 23 deletions

View File

@ -1,11 +1,13 @@
{% extends 'base.html' %}
{% block content %}
<section class="panel">
<article>
<h1>{{ user.first_name }} {{ user.last_name }}</h1>
<section>
<p>{{ user.email }}</p>
<p>
<a href="{% url 'account-update' user.id %}">Update email/change password</a>
</p>
</section>
</article>
{% endblock %}

View File

@ -6,5 +6,59 @@
<header>
<h1>Hi, {{user.first_name}}</h1>
</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</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">
{{entry.notes}}
<em>for</em>
<a href="{% url 'employee-detail' entry.employee.pk %}">{{entry.employee}}</a>
</p>
{% endfor %}
{% endfor %}
</section>
</article>
{% endblock %}

View File

@ -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):

View File

@ -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)

View File

@ -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',
}),
}

View 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'),
},
),
]

View 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',
),
]

View File

@ -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})

View File

@ -31,6 +31,17 @@
<p><strong>Initial comments</strong>:<p>
<blockquote>{{employee.initial_comments|linebreaksbr}}</blockquote>
</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">
<h3>To-do's</h3>
{% include "board/todo_list.html" with todo_list=employee.todo_set.all %}
@ -45,11 +56,14 @@
<p>
<a class="action-button" href="{% url 'entry-create' employee.pk %}">Add an Entry</a>
</p>
{% for entry in employee.logentry_set.all %}
<p>
<span>{{entry.created_at|date:"SHORT_DATE_FORMAT"}}&mdash;</span>
<span>{{entry.notes}}</span>
</p>
{% regroup employee.logentry_set.all by created_at.date as activity %}
{% for date in activity %}
<h5>{{date.grouper|date:"D, M j"}}</h5>
{% for entry in date.list %}
<p class="activity__item">
<span>{{entry.created_at|time:"H:i"}}</span>
<span>{{entry.notes}}</span></p>
{% endfor %}
{% endfor %}
</section>
</article>

View 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 %}

View 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 'event-list' %}">cancel</a>
</p>
</form>
</section>
</article>
{% endblock %}

View 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 %}

View 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 'event-list' %}">cancel</a>
</p>
</form>
</section>
</article>
{% endblock %}

View File

@ -0,0 +1,24 @@
{% 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>
</article>
{% endblock %}

View File

@ -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'),
])),
]

View File

@ -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):
@ -171,3 +172,26 @@ class TodoDeleteView(LoginRequiredMixin, DeleteView):
class TodoDeleteDoneView(LoginRequiredMixin, TemplateView):
template_name = 'board/item_deleted.html'
# Events
class EventListView(LoginRequiredMixin, ListView):
model = Event
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')

17
onboard/middleware.py Normal file
View 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)

View File

@ -53,6 +53,7 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'onboard.middleware.TimezoneMiddleware',
]
ROOT_URLCONF = 'onboard.urls'

View File

@ -25,7 +25,7 @@ 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 = []
ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'xcarbon.lan']
# Application definition
@ -51,6 +51,7 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'onboard.middleware.TimezoneMiddleware',
]
ROOT_URLCONF = 'onboard.urls'

View File

@ -2,7 +2,7 @@
--white: #fdfdff;
--black: #393d3f;
--grey: #a7b4bb;
--blue: #2288a2;
--blue: #10638c;
}
html {
@ -63,7 +63,7 @@ blockquote {
border-left: 2px solid var(--blue);
}
header {
body > header {
padding: 1rem;
}
@ -95,6 +95,7 @@ article {
.navbar__mobile {
display: none;
font-weight: 900;
}
.navbar__menu {
@ -203,6 +204,8 @@ article {
/* 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 +226,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 +255,10 @@ li label {
display: inline-block;
}
input:focus, textarea:focus, select:focus {
border-color: var(--blue) !important;
}
@ -301,3 +304,26 @@ hgroup {
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);
}

View File

@ -26,6 +26,8 @@
<input name="q" type="search" placeholder="Search...">
</form>
<div class="nav__employees">
<a href="{% url 'profile-detail' %}">Today</a>
<a href="{% url 'event-list' %}">Events</a>
<a href="{% url 'employee-list' %}">Employees</a>
</div>
<div class="nav__auth">
@ -37,6 +39,7 @@
<a class="action-button" href="{% url 'logout' %}">Logout</a>
</div>
{% else %}
<span>OnBoard</span>
<a class="nav__login" class="action-button" href="{% url 'login' %}">Login</a>
{% endif %}
</nav>
@ -51,12 +54,17 @@
</form>
</dt>
<br>
<dt><a href="{% url 'profile-detail' %}">Today</a></dt>
<br>
<dt><a href="{% url 'event-list' %}">Events</a></dt>
<br>
<dt><a href="{% url 'employee-list' %}">Employees</a></dt>
<br>
<dt><a class="action-button" href="{% url 'logout' %}">Logout</a></dt>
</dl>
</details>
{% else %}
<span>OnBoard</span>
<a class="nav__login" class="action-button" href="{% url 'login' %}">Login</a>
{% endif %}
</nav>