Repository: dotkom/onlineweb4

Issue:
Check if user is attending before trying to attend user
If a user has a cached version of the website available with the "Attend"-button visible, and they attend from another device, then try to attend from the first device, you'll get a stacktrace due to unique constraint failed from the database, since the user already attends the event.

This should not be the case, and we should add a check for if the user is already attending.


Discussion:

Resolving patch:
diff --git a/apps/events/models.py b/apps/events/models.py
--- a/apps/events/models.py
+++ b/apps/events/models.py
@@ -598,6 +598,7 @@ def is_eligible_for_signup(self, user):
         """
         Checks if a user can attend a specific event
         This method checks for:
+            Already attended
             Waitlist
             Room on event
             Rules
@@ -617,6 +618,12 @@ def is_eligible_for_signup(self, user):
 
         response = {'status': False, 'message': '', 'status_code': None}
 
+        # User is already an attendee
+        if self.attendees.filter(user=user).exists():
+            response['message'] = 'Du er allerede meldt på dette arrangementet.'
+            response['status_code'] = 404
+            return response
+
         # Registration closed
         if timezone.now() > self.registration_end:
             response['message'] = _('Påmeldingen er ikke lenger åpen.')


Test patch:
diff --git a/apps/events/tests/all_tests.py b/apps/events/tests/all_tests.py
--- a/apps/events/tests/all_tests.py
+++ b/apps/events/tests/all_tests.py
@@ -35,7 +35,6 @@ def setUp(self):
         self.event = G(Event, title='Sjakkturnering')
         self.attendance_event = G(AttendanceEvent, event=self.event, max_capacity=2)
         self.user = G(User, username='ola123', ntnu_username='ola123ntnu', first_name="ola", last_name="nordmann")
-        self.attendee = G(Attendee, event=self.attendance_event, user=self.user)
         self.logger = logging.getLogger(__name__)
         # Setting registration start 1 hour in the past, end one week in the future.
         self.now = timezone.now()
@@ -58,6 +57,7 @@ def testAttendanceEventUnicodeIsCorrect(self):
     #
 
     def testAttendeeAndWaitlistQS(self):
+        G(Attendee, event=self.attendance_event, user=self.user)
         self.logger.debug("Testing attendee queryset")
         user1 = G(User, username="jan", first_name="jan")
         G(Attendee, event=self.attendance_event, user=user1)
@@ -78,6 +78,7 @@ def testReservedSeats(self):
         self.assertEqual(self.attendance_event.number_of_reserved_seats_taken, 2)
 
     def testNumberOfTakenSeats(self):
+        G(Attendee, event=self.attendance_event, user=self.user, attended=False)
         # Increase event capacity so we have more room to test with
         self.attendance_event.max_capacity = 5
         self.assertEqual(self.attendance_event.number_of_attendees, 1)
@@ -102,9 +103,10 @@ def testNumberOfTakenSeats(self):
     #
 
     def testAttendeeUnicodeIsCorrect(self):
+        attendee = G(Attendee, event=self.attendance_event, user=self.user, attended=False)
         self.logger.debug("Testing testing on Attendee with dynamic fixtures")
-        self.assertEqual(self.attendee.__str__(), self.user.get_full_name())
-        self.assertNotEqual(self.attendee.__str__(), 'Ola Normann')
+        self.assertEqual(attendee.__str__(), self.user.get_full_name())
+        self.assertNotEqual(attendee.__str__(), 'Ola Normann')
 
     def testSignUpWithNoRulesNoMarks(self):
         self.logger.debug("Testing signup with no rules and no marks.")
@@ -260,13 +262,11 @@ def testFutureAccessTrumpsOffset(self):
     #
 
     def testMommyNotAttended(self):
-        self.attendee.attended = False
-        self.attendee.save()
+        G(Attendee, event=self.attendance_event, user=self.user, attended=False)
         self.assertEqual([self.user], self.attendance_event.not_attended())
 
     def testMommyAttended(self):
-        self.attendee.attended = True
-        self.attendee.save()
+        G(Attendee, event=self.attendance_event, user=self.user, attended=True)
         self.assertEqual([], self.attendance_event.not_attended())
 
     def testMommyActiveEvents(self):
@@ -307,8 +307,7 @@ def testMommyEventNotDone(self):
         self.assertFalse(SetEventMarks.active_events())
 
     def testMommySetMarks(self):
-        self.attendee.attended = False
-        self.attendee.save()
+        G(Attendee, event=self.attendance_event, user=self.user, attended=False)
         self.attendance_event.marks_has_been_set = False
         self.attendance_event.automatically_set_marks = True
         self.event.event_end = timezone.now() - datetime.timedelta(days=1)
@@ -321,8 +320,7 @@ def testMommySetMarks(self):
         self.assertEqual(self.user, MarkUser.objects.get().user)
 
     def testMommyEveryoneAttendend(self):
-        self.attendee.attended = True
-        self.attendee.save()
+        G(Attendee, event=self.attendance_event, user=self.user, attended=True)
         self.attendance_event.marks_has_been_set = False
         self.attendance_event.automatically_set_marks = True
         self.event.event_end = timezone.now() - datetime.timedelta(days=1)
@@ -335,8 +333,7 @@ def testMommyEveryoneAttendend(self):
         self.assertFalse(MarkUser.objects.all())
 
     def testMommyGenerateEmptyMessage(self):
-        self.attendee.attended = True
-        self.attendee.save()
+        G(Attendee, event=self.attendance_event, user=self.user, attended=True)
         self.attendance_event.marks_has_been_set = False
         self.attendance_event.automatically_set_marks = True
         self.event.event_end = timezone.now() - datetime.timedelta(days=1)
@@ -349,8 +346,7 @@ def testMommyGenerateEmptyMessage(self):
         self.assertFalse(message.results_message)
 
     def testMommyGenerateMessage(self):
-        self.attendee.attended = False
-        self.attendee.save()
+        G(Attendee, event=self.attendance_event, user=self.user, attended=False)
         self.attendance_event.marks_has_been_set = False
         self.attendance_event.automatically_set_marks = True
         self.event.event_end = timezone.now() - datetime.timedelta(days=1)
diff --git a/apps/events/tests/view_tests.py b/apps/events/tests/view_tests.py
--- a/apps/events/tests/view_tests.py
+++ b/apps/events/tests/view_tests.py
@@ -305,6 +305,28 @@ def test_attend_successfully(self):
         self.assertRedirects(response, event.get_absolute_url())
         self.assertInMessages('Du er nå meldt på arrangementet.', response)
 
+    def test_attend_twice(self):
+        event = G(Event)
+        G(
+            AttendanceEvent,
+            event=event,
+            registration_start=timezone.now() - timedelta(days=1),
+            registration_end=timezone.now() + timedelta(days=1)
+        )
+        url = reverse('attend_event', args=(event.id,))
+        # django-recatpcha magic when RECAPTCHA_TESTING=True
+        form_params = {'g-recaptcha-response': 'PASSED'}
+        G(AllowedUsername, username=self.user.ntnu_username,
+          expiration_date=timezone.now() + timedelta(days=1))
+        self.user.mark_rules = True
+        self.user.save()
+
+        self.client.post(url, form_params, follow=True)
+        response = self.client.post(url, form_params, follow=True)
+
+        self.assertRedirects(response, event.get_absolute_url())
+        self.assertInMessages('Du er allerede meldt på dette arrangementet.', response)
+
 
 class EventsUnattend(EventsTestMixin, TestCase):
     def test_unattend_not_attended(self):