diff --git a/RIGS/forms.py b/RIGS/forms.py
index 9a5f60b6..8a840166 100644
--- a/RIGS/forms.py
+++ b/RIGS/forms.py
@@ -121,7 +121,7 @@ class EventForm(forms.ModelForm):
fields = ['is_rig', 'name', 'venue', 'start_time', 'end_date', 'start_date',
'end_time', 'meet_at', 'access_at', 'description', 'notes', 'mic',
'person', 'organisation', 'dry_hire', 'checked_in_by', 'status',
- 'purchase_order', 'collector']
+ 'purchase_order', 'collector', 'forum_url']
class BaseClientEventAuthorisationForm(forms.ModelForm):
diff --git a/RIGS/migrations/0050_event_forum_url.py b/RIGS/migrations/0050_event_forum_url.py
new file mode 100644
index 00000000..b0589a9c
--- /dev/null
+++ b/RIGS/migrations/0050_event_forum_url.py
@@ -0,0 +1,19 @@
+# Generated by Django 3.2.19 on 2023-06-27 11:28
+
+import RIGS.models
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('RIGS', '0049_auto_20230529_1123'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='event',
+ name='forum_url',
+ field=models.URLField(blank=True, default='', validators=[RIGS.models.validate_forum_url]),
+ ),
+ ]
diff --git a/RIGS/models.py b/RIGS/models.py
index ea18e95e..b11eecc5 100644
--- a/RIGS/models.py
+++ b/RIGS/models.py
@@ -308,6 +308,14 @@ class EventManager(models.Manager):
return qs
+def validate_forum_url(value):
+ if not value:
+ return # Required error is done the field
+ obj = urlparse(value)
+ if obj.hostname not in ('forum.nottinghamtec.co.uk'):
+ raise ValidationError('URL must point to a location on the TEC Forum')
+
+
@reversion.register(follow=['items'])
class Event(models.Model, RevisionMixin):
# Done to make it much nicer on the database
@@ -357,6 +365,8 @@ class Event(models.Model, RevisionMixin):
auth_request_at = models.DateTimeField(null=True, blank=True)
auth_request_to = models.EmailField(blank=True, default='')
+ forum_url = models.URLField(default='', blank=True, validators=[validate_forum_url])
+
@property
def display_id(self):
if self.pk:
@@ -505,7 +515,7 @@ class Event(models.Model, RevisionMixin):
return reverse('event_detail', kwargs={'pk': self.pk})
def __str__(self):
- return f"{self.display_id}: {self.name}"
+ return f"{self.display_id} | {self.name}"
def clean(self):
errdict = {}
diff --git a/RIGS/templates/event_form.html b/RIGS/templates/event_form.html
index 108ed4e5..fd1b462b 100644
--- a/RIGS/templates/event_form.html
+++ b/RIGS/templates/event_form.html
@@ -231,7 +231,7 @@
-
+
{% render_field form.start_date class+="form-control" %}
@@ -246,7 +246,7 @@
-
+
{% render_field form.end_date class+="form-control" %}
@@ -334,12 +334,26 @@
+
+
diff --git a/RIGS/templates/partials/event_details.html b/RIGS/templates/partials/event_details.html
index 1b5a4e7d..6d7a249d 100644
--- a/RIGS/templates/partials/event_details.html
+++ b/RIGS/templates/partials/event_details.html
@@ -77,6 +77,15 @@
PO
{{ object.purchase_order }}
{% endif %}
+
+
Forum Thread
+ {% if object.forum_url %}
+
{{object.forum_url}}
+ {% else %}
+
Create Forum Thread
+ {% endif %}
diff --git a/RIGS/urls.py b/RIGS/urls.py
index 9a5a1f7c..8793b741 100644
--- a/RIGS/urls.py
+++ b/RIGS/urls.py
@@ -110,6 +110,9 @@ urlpatterns = [
path('event/
/checkin/add/', login_required(views.EventCheckInOverride.as_view()),
name='event_checkin_override'),
+ path('event//thread/', permission_required_with_403('RIGS.change_event')(views.CreateForumThread.as_view()), name='event_thread'),
+ path('event/webhook/', views.RecieveForumWebhook.as_view(), name='webhook_recieve'),
+
# Finance
path('invoice/', permission_required_with_403('RIGS.view_invoice')(views.InvoiceIndex.as_view()),
name='invoice_list'),
diff --git a/RIGS/views/rigboard.py b/RIGS/views/rigboard.py
index d60f0c2b..6a9072a0 100644
--- a/RIGS/views/rigboard.py
+++ b/RIGS/views/rigboard.py
@@ -3,6 +3,12 @@ import datetime
import re
import premailer
import simplejson
+import urllib
+import hmac
+import hashlib
+
+from envparse import env
+from bs4 import BeautifulSoup
from django.conf import settings
from django.contrib import messages
@@ -19,6 +25,7 @@ from django.urls import reverse_lazy
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views import generic
+from django.views.decorators.csrf import csrf_exempt
from PyRIGS import decorators
from PyRIGS.views import OEmbedView, is_ajax, ModalURLMixin, PrintView, get_related
@@ -377,3 +384,41 @@ class EventAuthoriseRequestEmailPreview(generic.DetailView):
context['to_name'] = self.request.GET.get('to_name', None)
context['target'] = 'event_authorise_form_preview'
return context
+
+
+class CreateForumThread(generic.base.RedirectView):
+ permanent = False
+
+ def get_redirect_url(self, *args, **kwargs):
+ event = get_object_or_404(models.Event, pk=kwargs['pk'])
+
+ if event.forum_url:
+ return event.forum_url
+
+ params = {
+ 'title': str(event),
+ 'body': f'https://rigs.nottinghamtec.co.uk/event/{event.pk}',
+ 'category': 'rig-info'
+ }
+ return f'https://forum.nottinghamtec.co.uk/new-topic?{urllib.parse.urlencode(params)}'
+
+
+class RecieveForumWebhook(generic.View):
+ @method_decorator(csrf_exempt)
+ def dispatch(self, request, *args, **kwargs):
+ return super().dispatch(request, *args, **kwargs)
+
+ def post(self, request, *args, **kwargs):
+ computed = f"sha256={hmac.new(env('FORUM_WEBHOOK_SECRET').encode(), request.body, hashlib.sha256).hexdigest()}"
+ if not hmac.compare_digest(request.headers.get('X-Discourse-Event-Signature'), computed):
+ return HttpResponseForbidden('Invalid signature header')
+ # Check if this is the right kind of event. The webhook filters by category on the forum side
+ if request.headers.get('X-Discourse-Event') == "topic_created":
+ body = simplejson.loads(request.body.decode('utf-8'))
+ event_id = int(body['topic']['title'][1:6]) # find the ID, force convert it to an int to eliminate leading zeros
+ event = models.Event.objects.filter(pk=event_id).first()
+ if event:
+ event.forum_url = f"https://forum.nottinghamtec.co.uk/t/{body['topic']['slug']}"
+ event.save()
+ return HttpResponse(status=202)
+ return HttpResponse(status=204)