feat(activities,people): Allow people to attend activities

resolve #13
This commit is contained in:
James Graham
2020-02-20 09:38:34 +00:00
parent 0d5d04902e
commit 65f46a66f1
7 changed files with 190 additions and 6 deletions

View File

@@ -0,0 +1,19 @@
# Generated by Django 2.2.10 on 2020-02-19 15:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('people', '0005_user_one_person'),
('activities', '0003_rename_activity_series_fk'),
]
operations = [
migrations.AddField(
model_name='activity',
name='attendance_list',
field=models.ManyToManyField(related_name='activities', to='people.Person'),
),
]

View File

@@ -1,6 +1,9 @@
from django.db import models from django.db import models
from people import models as people_models
class ActivityType(models.Model): class ActivityType(models.Model):
""" """
Representation of the type of activity being conducted. Representation of the type of activity being conducted.
@@ -33,7 +36,7 @@ class ActivityMedium(models.Model):
class ActivitySeries(models.Model): class ActivitySeries(models.Model):
""" """
A series of related :class:`Activity`s A series of related :class:`Activity`s.
""" """
class Meta: class Meta:
verbose_name_plural = 'activity series' verbose_name_plural = 'activity series'
@@ -58,7 +61,7 @@ class ActivitySeries(models.Model):
class Activity(models.Model): class Activity(models.Model):
""" """
An instance of an activity - e.g. a workshop An instance of an activity - e.g. a workshop.
""" """
class Meta: class Meta:
verbose_name_plural = 'activities' verbose_name_plural = 'activities'
@@ -73,15 +76,19 @@ class Activity(models.Model):
on_delete=models.PROTECT, on_delete=models.PROTECT,
blank=True, null=True) blank=True, null=True)
#: What type of activity does this series represent? #: What type of activity is this?
type = models.ForeignKey(ActivityType, type = models.ForeignKey(ActivityType,
on_delete=models.PROTECT, on_delete=models.PROTECT,
blank=False, null=False) blank=False, null=False)
#: How are activities in this series typically conducted? #: How was this activity conducted?
medium = models.ForeignKey(ActivityMedium, medium = models.ForeignKey(ActivityMedium,
on_delete=models.PROTECT, on_delete=models.PROTECT,
blank=False, null=False) blank=False, null=False)
#: Who attended this activity?
attendance_list = models.ManyToManyField(people_models.Person,
related_name='activities')
def __str__(self) -> str: def __str__(self) -> str:
return self.name return self.name

View File

@@ -10,6 +10,20 @@
</ol> </ol>
</nav> </nav>
{% if user_is_attending %}
<button class="btn btn-danger"
onclick="clickCancelAttend();">
Cancel Attendance
</button>
{% else %}
<button class="btn btn-success"
onclick="clickAttend();">
Attend
</button>
{% endif %}
<hr> <hr>
<dl> <dl>
@@ -23,4 +37,91 @@
<dd>{{ activity.medium }}</dd> <dd>{{ activity.medium }}</dd>
</dl> </dl>
<hr>
<table class="table table-borderless">
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
{% for person in activity.attendance_list.all %}
<tr>
<td>{{ person }}</td>
<td>
<a class="btn btn-sm btn-info"
href="{% url 'people:person.detail' pk=person.pk %}">Profile</a>
</td>
</tr>
{% empty %}
<tr>
<td>No records</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
{% block extra_script %}
<script type="application/javascript">
/**
* Get the value of a named cookie.
*/
function getCookie(name) {
for (const cookie of document.cookie.split(';')) {
const tokens = cookie.split('=');
if (tokens[0].trim() === name) {
return tokens[1].trim();
}
}
return null;
}
/**
* Submit that user is attending this activity.
*/
function clickAttend() {
$.ajax({
url: '{% url "activities:activity.attendance" pk=activity.pk %}',
type: 'POST',
contentType: 'application/json',
headers: {
'X-CSRFToken': getCookie('csrftoken')
},
data: JSON.stringify({
pk: {{ request.user.person.pk }}
}),
success: function() {
location.reload()
}
})
}
/**
* Submit that user is not attending this activity.
*/
function clickCancelAttend() {
$.ajax({
url: '{% url "activities:activity.attendance" pk=activity.pk %}',
type: 'DELETE',
contentType: 'application/json',
headers: {
'X-CSRFToken': getCookie('csrftoken')
},
data: JSON.stringify({
pk: {{ request.user.person.pk }}
}),
success: function() {
location.reload()
}
})
}
</script>
{% endblock %} {% endblock %}

View File

@@ -21,4 +21,8 @@ urlpatterns = [
path('activities/<int:pk>', path('activities/<int:pk>',
views.ActivityDetailView.as_view(), views.ActivityDetailView.as_view(),
name='activity.detail'), name='activity.detail'),
path('activities/<int:pk>/attendance',
views.ActivityAttendanceView.as_view(),
name='activity.attendance'),
] ]

View File

@@ -1,9 +1,14 @@
""" """
Views for displaying / manipulating models within the Activities app. Views for displaying / manipulating models within the Activities app.
""" """
from django.views.generic import DetailView, ListView import json
from django.http import HttpResponse
from django.views.generic import DetailView, ListView, View
from django.views.generic.detail import SingleObjectMixin
from . import models from . import models
from people import models as people_models
class ActivitySeriesListView(ListView): class ActivitySeriesListView(ListView):
@@ -38,3 +43,44 @@ class ActivityDetailView(DetailView):
""" """
model = models.Activity model = models.Activity
template_name = 'activities/activity/detail.html' template_name = 'activities/activity/detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['user_is_attending'] = self.object.attendance_list.filter(pk=self.request.user.person.pk).exists()
return context
class ActivityAttendanceView(SingleObjectMixin, View):
"""
View to add or delete attendance of an activity.
"""
model = models.Activity
def post(self, request, *args, **kwargs):
self.object = self.get_object()
if request.is_ajax():
data = json.loads(request.body)
person = people_models.Person.objects.get(pk=data['pk'])
self.object.attendance_list.add(person)
return HttpResponse(status=204)
pass
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
if request.is_ajax():
data = json.loads(request.body)
person = people_models.Person.objects.get(pk=data['pk'])
self.object.attendance_list.remove(person)
return HttpResponse(status=204)
pass

View File

@@ -234,3 +234,11 @@ CONSTANCE_CONFIG_FIELDSETS = {
} }
CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend' CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend'
# Bootstrap settings
# See https://django-bootstrap4.readthedocs.io/en/latest/settings.html
BOOTSTRAP4 = {
'include_jquery': 'full',
}

View File

@@ -7,7 +7,6 @@
<html lang="{{ LANGUAGE_CODE|default:'en_us' }}"> <html lang="{{ LANGUAGE_CODE|default:'en_us' }}">
<head> <head>
<!-- Required meta tags --> <!-- Required meta tags -->
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">