diff --git a/AKModel/admin.py b/AKModel/admin.py index ebc5df1dcac1e7bc77550dd90394ca67782a735e..49231c65e74c63212ee59a10b6423de43bc7a644 100644 --- a/AKModel/admin.py +++ b/AKModel/admin.py @@ -4,6 +4,7 @@ from django.db.models import Count, F from django.shortcuts import render from django.utils import timezone from django.utils.translation import gettext_lazy as _ +from simple_history.admin import SimpleHistoryAdmin from AKModel.availability import Availability from AKModel.models import Event, AKOwner, AKCategory, AKTrack, AKTag, AKRequirement, AK, AKSlot, Room @@ -113,7 +114,7 @@ class WishFilter(SimpleListFilter): @admin.register(AK) -class AKAdmin(admin.ModelAdmin): +class AKAdmin(SimpleHistoryAdmin): model = AK list_display = ['name', 'short_name', 'category', 'track', 'is_wish', 'interest', 'event'] list_filter = ['category', WishFilter, 'event'] diff --git a/AKModel/migrations/0032_AK_history.py b/AKModel/migrations/0032_AK_history.py new file mode 100644 index 0000000000000000000000000000000000000000..9b10391d813949dc42a67b707630289aa771432b --- /dev/null +++ b/AKModel/migrations/0032_AK_history.py @@ -0,0 +1,65 @@ +# Generated by Django 2.2.6 on 2020-05-11 22:27 + +import django.db.models.deletion +import simple_history.models +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('AKModel', '0031_event_ordering'), + ] + + operations = [ + migrations.CreateModel( + name='HistoricalAK', + fields=[ + ('id', models.IntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), + ('name', models.CharField(help_text='Name of the AK', max_length=256, verbose_name='Name')), + ('short_name', models.CharField(blank=True, help_text='Name displayed in the schedule', max_length=64, + verbose_name='Short Name')), + ('description', + models.TextField(blank=True, help_text='Description of the AK', verbose_name='Description')), + ('link', models.URLField(blank=True, help_text='Link to wiki page', verbose_name='Web Link')), + ('reso', models.BooleanField(default=False, help_text='Intends to submit a resolution', + verbose_name='Resolution Intention')), + ('present', models.BooleanField(help_text='Present results of this AK', null=True, + verbose_name='Present this AK')), + ('notes', models.TextField(blank=True, + help_text='Notes to organizers. These are public. For private notes, please send an e-mail.', + verbose_name='Organizational Notes')), + ('interest', + models.IntegerField(default=-1, help_text='Expected number of people', verbose_name='Interest')), + ('interest_counter', + models.IntegerField(default=0, help_text='People who have indicated interest online', + verbose_name='Interest Counter')), + ('history_id', models.AutoField(primary_key=True, serialize=False)), + ('history_date', models.DateTimeField()), + ('history_change_reason', models.CharField(max_length=100, null=True)), + ('history_type', + models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), + ('category', + models.ForeignKey(blank=True, db_constraint=False, help_text='Category of the AK', null=True, + on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', + to='AKModel.AKCategory', verbose_name='Category')), + ('event', models.ForeignKey(blank=True, db_constraint=False, help_text='Associated event', null=True, + on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', + to='AKModel.Event', verbose_name='Event')), + ('history_user', + models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', + to=settings.AUTH_USER_MODEL)), + ('track', + models.ForeignKey(blank=True, db_constraint=False, help_text='Track the AK belongs to', null=True, + on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', + to='AKModel.AKTrack', verbose_name='Track')), + ], + options={ + 'verbose_name': 'historical AK', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': 'history_date', + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + ] diff --git a/AKModel/models.py b/AKModel/models.py index 596d92e6b83f3b842d17a6a10eabfca25850159b..c46c90f9ddd3c9797a7bff58509d9d79b1569406 100644 --- a/AKModel/models.py +++ b/AKModel/models.py @@ -7,6 +7,7 @@ from django.db import models from django.utils import timezone from django.utils.text import slugify from django.utils.translation import gettext_lazy as _ +from simple_history.models import HistoricalRecords from timezone_field import TimeZoneField @@ -224,6 +225,8 @@ class AK(models.Model): event = models.ForeignKey(to=Event, on_delete=models.CASCADE, verbose_name=_('Event'), help_text=_('Associated event')) + history = HistoricalRecords() + class Meta: verbose_name = _('AK') verbose_name_plural = _('AKs') diff --git a/AKPlanning/settings.py b/AKPlanning/settings.py index d3d55d88d2d2c693ef77ae58452468ae80745ab9..d9bcb96dd3c48be20c310689fccfd5a018216195 100644 --- a/AKPlanning/settings.py +++ b/AKPlanning/settings.py @@ -47,6 +47,7 @@ INSTALLED_APPS = [ 'fontawesome_5', 'timezone_field', 'rest_framework', + 'simple_history', ] MIDDLEWARE = [ @@ -58,6 +59,7 @@ MIDDLEWARE = [ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'simple_history.middleware.HistoryRequestMiddleware', ] ROOT_URLCONF = 'AKPlanning.urls' diff --git a/AKSubmission/templates/AKSubmission/ak_detail.html b/AKSubmission/templates/AKSubmission/ak_detail.html index fc095aad679721dd8447341e475f591d20b73fb2..32bbcaaae91fd9b9ad0b2b5f57cf3db1e7562f83 100644 --- a/AKSubmission/templates/AKSubmission/ak_detail.html +++ b/AKSubmission/templates/AKSubmission/ak_detail.html @@ -36,6 +36,8 @@ {% if ak.link != "" %} <a href="{{ ak.link }}" class="btn btn-info">{% fa5_icon 'external-link-alt' 'fas' %}</a> {% endif %} + <a href="{% url 'submit:ak_history' event_slug=ak.event.slug pk=ak.pk %}" + class="btn btn-light">{% fa5_icon 'clock' 'fas' %}</a> {% if ak.event.active %} <a href="{% url 'submit:ak_edit' event_slug=ak.event.slug pk=ak.pk %}" class="btn btn-success">{% fa5_icon 'pencil-alt' 'fas' %}</a> diff --git a/AKSubmission/templates/AKSubmission/ak_history.html b/AKSubmission/templates/AKSubmission/ak_history.html new file mode 100644 index 0000000000000000000000000000000000000000..af42759b2b420ab19f3916ca017f3ae9fcfa87d8 --- /dev/null +++ b/AKSubmission/templates/AKSubmission/ak_history.html @@ -0,0 +1,64 @@ +{% extends 'AKSubmission/submission_base.html' %} +{% load tz %} + +{% load i18n %} +{% load fontawesome_5 %} + +{% load tags_AKSubmission %} +{% load tags_AKModel %} + + +{% block title %}{% trans "AKs" %}: {{ ak.event.name }} - {% trans "AK" %}: {{ ak.name }}{% endblock %} + +{% block breadcrumbs %} + {% include "AKSubmission/submission_breadcrumbs.html" %} + <li class="breadcrumb-item"><a + href="{% url 'submit:submission_overview' event_slug=ak.event.slug %}">{% trans "AK Submission" %}</a></li> + <li class="breadcrumb-item"><a + href='{% url 'submit:ak_detail' event_slug=ak.event.slug pk=ak.pk %}'>{{ ak.short_name }}</a></li> + <li class="breadcrumb-item active">{% trans 'History' %}</li> +{% endblock %} + +{% block content %} + {% include "AKSubmission/messages.html" %} + + <div class="float-right"> + <a href='{% url 'submit:ak_detail' event_slug=ak.event.slug pk=ak.pk %}' + class="btn btn-info">{% fa5_icon 'arrow-circle-left' 'fas' %}</a> + </div> + + <h2>{% if ak.wish %}{% trans "AK Wish" %}: {% endif %}{{ ak.name }} ({% trans 'History' %})</h2> + <table id="akTable" class="table table-striped"> + <thead> + <tr> + <th>{% trans "Name" %}</th> + <th>{% trans 'Category' %}</th> + <th>{% trans 'Track' %}</th> + <th>{% trans 'Time' %}</th> + </tr> + </thead> + <tbody> + {% for h in ak.history.all %} + <tr> + <td> + <b>{{ h.name }}</b> + {% if h.present %} + <span class="badge badge-dark badge-pill" + title="{% trans 'present this AK' %}">{% fa5_icon "bullhorn" 'fas' %}</span> + {% endif %} + {% if h.reso %} + <span class="badge badge-dark badge-pill" + title="{% trans 'Reso' %}">{% fa5_icon "scroll" 'fas' %}</span> + {% endif %} + </td> + <td>{% category_linked_badge h.category event.slug %}</td> + <td><span class="badge badge-success badge-pill">{{ h.track }}</span></td> + <td>{{ h.history_date | timezone:ak.event.timezone | date:"Y-m-d H:i:s" }}</td> + </tr> + <tr> + <td colspan="4" class="small">{{ h.description }}</td> + </tr> + {% endfor %} + </tbody> + </table> +{% endblock %} diff --git a/AKSubmission/urls.py b/AKSubmission/urls.py index 7f55980bbc62f92495273fd936b1459ac6e2ad41..3528b45d09e7486d9fc6b56363bc86dce56a8dd0 100644 --- a/AKSubmission/urls.py +++ b/AKSubmission/urls.py @@ -10,6 +10,7 @@ urlpatterns = [ include([ path('', views.SubmissionOverviewView.as_view(), name='submission_overview'), path('ak/<int:pk>/', views.AKDetailView.as_view(), name='ak_detail'), + path('ak/<int:pk>/history/', views.AKHistoryView.as_view(), name='ak_history'), path('ak/<int:pk>/edit/', views.AKEditView.as_view(), name='ak_edit'), path('ak/<int:pk>/interest/', views.AKInterestView.as_view(), name='inc_interest'), path('ak/<int:pk>/add_slot/', views.AKSlotAddView.as_view(), name='akslot_add'), diff --git a/AKSubmission/views.py b/AKSubmission/views.py index e5cebeb7e66ddcf12ddd0813728c0bea18621407..78c2fcde1cb5ef512b53f1180145c51037e7459b 100644 --- a/AKSubmission/views.py +++ b/AKSubmission/views.py @@ -51,6 +51,12 @@ class AKDetailView(EventSlugMixin, DetailView): template_name = "AKSubmission/ak_detail.html" +class AKHistoryView(EventSlugMixin, DetailView): + model = AK + context_object_name = "ak" + template_name = "AKSubmission/ak_history.html" + + class AKListView(FilterByEventSlugMixin, ListView): model = AK context_object_name = "AKs" diff --git a/requirements.txt b/requirements.txt index b06f587734372a2314a549e8e849c6028d723d2a..801b147b3b84096259d661766ed1a65231855cfb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ django-fontawesome-5==1.0.16 django-split-settings==1.0.1 django-timezone-field==4.0 djangorestframework==3.11.0 +django-simple-history==2.10.0 \ No newline at end of file