From f00b99227408618f3cf58a8f9cfedb19de984bab Mon Sep 17 00:00:00 2001 From: Cara Salter Date: Wed, 15 Sep 2021 21:00:10 +0000 Subject: [PATCH 01/14] Make licensed projectionists not include PP --- projection/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projection/views.py b/projection/views.py index b598daa3..11d5f86c 100644 --- a/projection/views.py +++ b/projection/views.py @@ -44,7 +44,7 @@ def plist_detail(request): users = Projectionist.objects \ .select_related('user') - licensed = Q(pitinstances__pit_level__name_short__in=['PP', 'L']) + licensed = Q(pitinstances__pit_level__name_short__in=['L']) alumni = Q(user__groups__name="Alumni") context['current_users'] = users.exclude(alumni) From f3d602fb5dfbab8599c7aad40b60b541180739b7 Mon Sep 17 00:00:00 2001 From: Tom Nurse Date: Thu, 9 Sep 2021 15:40:41 -0400 Subject: [PATCH 02/14] Minor improvements for Slack beta --- slack/api.py | 10 ++++------ slack/views.py | 3 ++- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/slack/api.py b/slack/api.py index 575c568c..023af15c 100755 --- a/slack/api.py +++ b/slack/api.py @@ -7,7 +7,6 @@ from django.views.decorators.http import require_POST from django.views.decorators.csrf import csrf_exempt from django.contrib.auth import get_user_model -from django.contrib.auth.decorators import permission_required from slack_sdk import WebClient from slack_sdk.errors import SlackApiError @@ -241,7 +240,6 @@ def replace_message(channel, message_id, text=None, content=None): return {'ok': False, 'error': 'no_text'} -@permission_required('slack.manage_channel', raise_exception=True) def user_add(channel, users): """ Invite users to join a slack channel. The bot must be a member of the channel. @@ -265,7 +263,6 @@ def user_add(channel, users): return e.response -@permission_required('slack.manage_channel', raise_exception=True) def user_kick(channel, user): """ Remove a user from a slack channel. The bot must be a member of the channel. @@ -420,7 +417,8 @@ def load_app_home(user_id): ticket = rt_api.fetch_ticket(ticket_id) if ticket.get('message'): continue - tickets.append(ticket) + if ticket['Status'] in ['open', 'new', 'stalled']: + tickets.append(ticket) blocks = views.app_home(tickets) if not settings.SLACK_TOKEN: @@ -573,14 +571,14 @@ def __create_ticket(user, subject, description, topic): target = settings.SLACK_TARGET_TFED_DB user_email = user['user']['profile'].get('email', 'lnl-no-reply@wpi.edu') display_name = user['user']['profile']['real_name'] - resp = rt_api.create_ticket(topic, user_email, subject, description) + resp = rt_api.create_ticket(topic, user_email, subject, description + "\n\n- " + display_name) ticket_id = resp.get('id', None) if ticket_id: ticket_info = { "url": 'https://lnl-rt.wpi.edu/rt/Ticket/Display.html?id=' + ticket_id, "id": ticket_id, "subject": subject, - "description": description + "\n\n- " + display_name, + "description": description, "status": "New", "assignee": None, "reporter": user['user']['name'] diff --git a/slack/views.py b/slack/views.py index 8e5ea419..98c82dd9 100644 --- a/slack/views.py +++ b/slack/views.py @@ -454,7 +454,8 @@ def app_home(tickets): "block_id": str(ticket['id']), "text": { "type": "mrkdwn", - "text": "\n*Ticket #" + str(ticket['id']) + ": " + ticket['Subject'] + "*\nStatus » " + ticket['Status'] + "text": "\n*Ticket #" + str(ticket['id']) + ": " + ticket['Subject'] + "*\nStatus » " + + ticket['Status'].capitalize() }, "accessory": { "type": "overflow", From 058e00631293f06c6b7a7cded183b25a97c6e2eb Mon Sep 17 00:00:00 2001 From: Tom Nurse Date: Thu, 9 Sep 2021 16:39:31 -0400 Subject: [PATCH 03/14] Closes #434 --- docs/help/meetings/meeting-details.rst | 9 ++++++++- meetings/forms.py | 15 +++++++++++---- meetings/models.py | 2 +- meetings/tests.py | 21 +++++++++++++++++++++ meetings/urls.py | 1 + meetings/views.py | 14 ++++++++++++++ 6 files changed, 56 insertions(+), 6 deletions(-) diff --git a/docs/help/meetings/meeting-details.rst b/docs/help/meetings/meeting-details.rst index a2382200..053eb832 100644 --- a/docs/help/meetings/meeting-details.rst +++ b/docs/help/meetings/meeting-details.rst @@ -35,6 +35,13 @@ attendance records, and related files. #. Finally, click `Save Changes` +**To delete a meeting:** + +#. Follow the process above as if you were to edit the meeting. +#. Scroll down to the bottom of the page and click `Delete`. +#. Finally, click `Yes!` to confirm the action. + + .. _Update Attendance: Update Attendance @@ -47,4 +54,4 @@ select the listing to add their name to the list. To remove someone from the attendance list, click the trash can icon next to their name. -`Last Modified: May 17, 2021` +`Last Modified: September 9, 2021` diff --git a/meetings/forms.py b/meetings/forms.py index 95a735b3..595c0e10 100644 --- a/meetings/forms.py +++ b/meetings/forms.py @@ -3,7 +3,7 @@ from ajax_select.fields import AutoCompleteSelectMultipleField from crispy_forms.bootstrap import FormActions, Tab, TabHolder from crispy_forms.helper import FormHelper -from crispy_forms.layout import Button, Field, Layout, Submit +from crispy_forms.layout import Button, Field, Layout, Submit, HTML from django import forms from django.db.models import Q from django.forms.fields import SplitDateTimeField @@ -23,6 +23,15 @@ def __init__(self, *args, **kwargs): self.helper.form_class = 'form-horizontal' self.helper.form_tag = False self.helper.include_media = False + actions = FormActions( + Submit('save', 'Save Changes') + ) + if kwargs.get("instance", None): + actions = FormActions( + Submit('save', 'Save Changes'), + HTML(' Delete ' + % kwargs.get("instance").pk), + ) self.helper.layout = Layout( TabHolder( Tab( @@ -51,9 +60,7 @@ def __init__(self, *args, **kwargs): 'attachments_private' ), ), - FormActions( - Submit('save', 'Save Changes'), - ) + actions ) super(MeetingAdditionForm, self).__init__(*args, **kwargs) self.fields['duration'].widget.attrs['placeholder'] = "e.g. 1 minute" diff --git a/meetings/models.py b/meetings/models.py index 17bc8f73..8c793841 100644 --- a/meetings/models.py +++ b/meetings/models.py @@ -90,7 +90,7 @@ def get_absolute_url(self): return reverse('meetings:detail', args=[self.id]) def __str__(self): - return "Meeting For %s" % self.datetime.astimezone(timezone.get_current_timezone()).date() + return "Meeting for %s" % self.datetime.astimezone(timezone.get_current_timezone()).date() class Meta: ordering = ('-datetime',) diff --git a/meetings/tests.py b/meetings/tests.py index 58adf301..13843b1f 100644 --- a/meetings/tests.py +++ b/meetings/tests.py @@ -303,3 +303,24 @@ def test_mkcc_notice(self): self.assertRedirects(self.client.post(reverse("meetings:cc-email", args=[self.meeting.pk]), valid_data), reverse("meetings:detail", args=[self.meeting.pk]) + "#emails") + + def test_delete_event(self): + # By default, user should not have permission to delete an event + self.assertOk(self.client.get(reverse("meetings:delete", args=[self.meeting.pk])), 403) + + permission = Permission.objects.get(codename="edit_mtg") + self.user.user_permissions.add(permission) + + # Will also need list_mtgs permission for redirect + permission = Permission.objects.get(codename="list_mtgs") + self.user.user_permissions.add(permission) + + self.assertOk(self.client.get(reverse("meetings:delete", args=[self.meeting.pk]))) + + pk = self.meeting.pk + # Confirm that the user wants to delete the meeting, then redirect to the meeting list + self.assertRedirects(self.client.post(reverse("meetings:delete", args=[self.meeting.pk])), + reverse("meetings:list")) + + # Check that the meeting was actually deleted + self.assertFalse(models.Meeting.objects.filter(pk=pk).exists()) diff --git a/meetings/urls.py b/meetings/urls.py index 784d0a0a..52fece2a 100644 --- a/meetings/urls.py +++ b/meetings/urls.py @@ -18,5 +18,6 @@ url(r'^download/(?P\d+)/$', views.download_att, name="att-dl"), url(r'^file/(?P\d+)/$', views.modify_att, name="att-edit"), url(r'^rm/(?P\d+)/$', views.rm_att, name="att-rm"), + url(r'^delete/$', views.DeleteMeeting.as_view(), name="delete") ])) ] diff --git a/meetings/views.py b/meetings/views.py index 61c965fd..250226dc 100644 --- a/meetings/views.py +++ b/meetings/views.py @@ -4,6 +4,7 @@ from django.core.exceptions import PermissionDenied from django.core.paginator import InvalidPage, Paginator from django.db.models.aggregates import Count +from django.views.generic.edit import DeleteView from django.forms.models import inlineformset_factory from django.http import HttpResponseRedirect, HttpResponse from django.http.response import Http404 @@ -14,6 +15,7 @@ from email.encoders import encode_base64 from helpers.util import curry_class +from helpers.mixins import HasPermMixin, LoginRequiredMixin from data.views import serve_file from emails.generators import generate_notice_cc_email, generate_notice_email from events.forms import CCIForm @@ -228,6 +230,18 @@ def newattendance(request): return render(request, 'form_crispy_meetings.html', context) +class DeleteMeeting(LoginRequiredMixin, HasPermMixin, DeleteView): + """ Delete a meeting """ + model = Meeting + template_name = "form_delete_cbv.html" + msg = "Delete Meeting" + perms = 'meetings.edit_mtg' + pk_url_kwarg = "mtg_id" + + def get_success_url(self): + return reverse("meetings:list") + + @login_required def mknotice(request, mtg_id): """ Send a meeting notice """ From 7db18c2cf2af8634afe69107a6426f10729dc5cb Mon Sep 17 00:00:00 2001 From: Tom Nurse Date: Thu, 9 Sep 2021 17:51:32 -0400 Subject: [PATCH 04/14] Closes #479 --- meetings/forms.py | 18 ++++++++++-------- site_tmpl/emails/email_notice.html | 6 +++++- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/meetings/forms.py b/meetings/forms.py index 595c0e10..09c680b6 100644 --- a/meetings/forms.py +++ b/meetings/forms.py @@ -12,7 +12,7 @@ from natural_duration import NaturalDurationField from pagedown.widgets import PagedownWidget -from events.models import Event, Location +from events.models import Event2019, Location from meetings.models import (CCNoticeSend, Meeting, MeetingAnnounce, MtgAttachment) @@ -117,9 +117,10 @@ def __init__(self, meeting, *args, **kwargs): twodaysago = now + datetime.timedelta(days=-4) aweekfromnow = now + datetime.timedelta(days=9) self.meeting = meeting - self.fields["events"].queryset = Event.objects.filter(datetime_setup_complete__gte=twodaysago, approved=True, - datetime_setup_complete__lte=aweekfromnow).exclude( - Q(closed=True) | Q(cancelled=True)) + self.fields["events"].queryset = Event2019.objects.filter( + datetime_setup_complete__gte=twodaysago, approved=True, + datetime_setup_complete__lte=aweekfromnow + ).exclude(Q(closed=True) | Q(cancelled=True)) self.helper = FormHelper() self.helper.form_class = 'form-horizontal' self.helper.label_class = 'col-lg-2' @@ -149,7 +150,7 @@ class Meta: 'message': PagedownWidget(), } - events = forms.ModelMultipleChoiceField(queryset=Event.objects.all(), required=False) + events = forms.ModelMultipleChoiceField(queryset=Event2019.objects.all(), required=False) class AnnounceCCSendForm(forms.ModelForm): @@ -173,9 +174,10 @@ def __init__(self, meeting, *args, **kwargs): ) super(AnnounceCCSendForm, self).__init__(*args, **kwargs) - self.fields["events"].queryset = Event.objects.filter(datetime_setup_complete__gte=twodaysago, approved=True, - datetime_setup_complete__lte=aweekfromnow).exclude( - Q(closed=True) | Q(cancelled=True)) + self.fields["events"].queryset = Event2019.objects.filter( + datetime_setup_complete__gte=twodaysago, approved=True, + datetime_setup_complete__lte=aweekfromnow + ).exclude(Q(closed=True) | Q(cancelled=True)) def save(self, commit=True): self.instance = super(AnnounceCCSendForm, self).save(commit=False) diff --git a/site_tmpl/emails/email_notice.html b/site_tmpl/emails/email_notice.html index b9b4bcc0..688ceed2 100644 --- a/site_tmpl/emails/email_notice.html +++ b/site_tmpl/emails/email_notice.html @@ -17,19 +17,23 @@

{{ object.message|markdown }}

+ {% if object.reverse_ordered_events.count %} +
+

Events

{% for e in object.reverse_ordered_events %} - + {% endfor %}
{{ e.datetime_start|localtime|date:"m-d D" }}{{ e.event_name|public_field }}{{ e.event_name|public_field }} {% for s in e.eventservices %}{{ s.shortname }} {% endfor %} {{ e.datetime_start|localtime|time:"TIME_FORMAT" }} - {{ e.datetime_end|localtime|time:"TIME_FORMAT" }} {{ e.location }}
+ {% endif %}
From b21f62d554d5f13978685c9a3807c0eb93cc6b90 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Fri, 6 Aug 2021 11:13:05 -0400 Subject: [PATCH 05/14] Update reportlab from 3.5.68 to 3.6.1 --- requirements_base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_base.txt b/requirements_base.txt index 5982620c..1d8daf53 100644 --- a/requirements_base.txt +++ b/requirements_base.txt @@ -47,7 +47,7 @@ PyPDF2==1.26.0 python-dateutil==2.8.1 pytz==2020.4 raven==6.10.0 -reportlab==3.5.68 +reportlab==3.6.1 six==1.15.0 sqlparse==0.4.1 tolerance==0.1.2 From ad6442ccdaa0708eba58966df5e55281c1e878ff Mon Sep 17 00:00:00 2001 From: Tom Nurse Date: Fri, 10 Sep 2021 19:00:11 -0400 Subject: [PATCH 06/14] Pin jsmin to tikitu/jsmin#34 Fixes breaking readthedocs builds due to removal of Python 2 support in setuptools 58 --- requirements_base.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements_base.txt b/requirements_base.txt index 1d8daf53..fcafd2e7 100644 --- a/requirements_base.txt +++ b/requirements_base.txt @@ -34,7 +34,8 @@ django-watson==1.5.5 docutils==0.16 html5lib==1.0.1 icalendar==4.0.7 -jsmin==2.2.2 +#jsmin==2.2.2 +git+https://github.com/serenecloud/jsmin@python3-only#egg=jsmin line-profiler==3.1.0 Markdown==3.3.3 markdown2==2.4.0 From b610d38c4974f5aad99e311ab3b0e32c72258554 Mon Sep 17 00:00:00 2001 From: Tom Nurse Date: Fri, 10 Sep 2021 19:51:20 -0400 Subject: [PATCH 07/14] Fixes readthedocs build error caused by lambdalisue/tolerance --- requirements_base.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/requirements_base.txt b/requirements_base.txt index fcafd2e7..95fb57af 100644 --- a/requirements_base.txt +++ b/requirements_base.txt @@ -35,7 +35,7 @@ docutils==0.16 html5lib==1.0.1 icalendar==4.0.7 #jsmin==2.2.2 -git+https://github.com/serenecloud/jsmin@python3-only#egg=jsmin +-e git+https://github.com/serenecloud/jsmin@python3-only#egg=jsmin line-profiler==3.1.0 Markdown==3.3.3 markdown2==2.4.0 @@ -51,7 +51,8 @@ raven==6.10.0 reportlab==3.6.1 six==1.15.0 sqlparse==0.4.1 -tolerance==0.1.2 +#tolerance==0.1.2 +-e git+https://github.com/WPI-LNL/tolerance@335211c974e5e1d85289e1a8745ce01794b05225#egg=tolerance xhtml2pdf==0.2.5 django-cas-ng==4.1.1 ldap3==2.8.1 From 2509bf95ed0069f38be29216a9916019d7a82ea2 Mon Sep 17 00:00:00 2001 From: Tom Nurse Date: Sat, 11 Sep 2021 16:49:17 -0400 Subject: [PATCH 08/14] Slack integration bug fix - Resolves an issue where ticket comments posted in Slack were not being sent to RT --- rt/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rt/api.py b/rt/api.py index 349d20ba..6c4e934b 100644 --- a/rt/api.py +++ b/rt/api.py @@ -124,7 +124,7 @@ def update_ticket(ticket_id, token, status=None, owner=None): """ if not status and not owner: - return None + return ['No change'] endpoint = host + 'ticket/' + str(ticket_id) payload = { From 1a96f2da98c26ec76eb14dafd7826fda1362d18b Mon Sep 17 00:00:00 2001 From: Tom Nurse Date: Sat, 11 Sep 2021 21:49:01 -0400 Subject: [PATCH 09/14] Another Slack integration bug fix - Resolves a key error that occurred when parsing ticket comment data from the Home tab --- slack/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slack/api.py b/slack/api.py index 023af15c..bd5c477d 100755 --- a/slack/api.py +++ b/slack/api.py @@ -501,7 +501,7 @@ def handle_interaction(request): return HttpResponse() elif callback_id == "ticket-comment-modal": ticket_id = payload['view']['blocks'][0]['block_id'] - comments = values['comments']['comment-action']['value'] + comments = values[ticket_id]['comment-action']['value'] user_id = payload['user']['id'] token = __retrieve_rt_token(user_id) __post_ticket_comment(ticket_id, user_id, comments, token) From 132c945f772c09a1281c868437112e2eaa142e13 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 7 Sep 2021 09:10:41 -0400 Subject: [PATCH 10/14] Update pillow from 8.3.0 to 8.3.2 --- requirements_base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_base.txt b/requirements_base.txt index 95fb57af..fb3dac4d 100644 --- a/requirements_base.txt +++ b/requirements_base.txt @@ -41,7 +41,7 @@ Markdown==3.3.3 markdown2==2.4.0 mechanize==0.4.5 Paste==3.5.0 -Pillow==8.3.0 +Pillow==8.3.2 pisa==3.0.33 pyPdf==1.13 PyPDF2==1.26.0 From 8a1eaa95d4255549eca68fa4918c8ff501c37118 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Sep 2021 23:52:10 +0000 Subject: [PATCH 11/14] Bump sqlparse from 0.4.1 to 0.4.2 Bumps [sqlparse](https://github.com/andialbrecht/sqlparse) from 0.4.1 to 0.4.2. - [Release notes](https://github.com/andialbrecht/sqlparse/releases) - [Changelog](https://github.com/andialbrecht/sqlparse/blob/master/CHANGELOG) - [Commits](https://github.com/andialbrecht/sqlparse/compare/0.4.1...0.4.2) --- updated-dependencies: - dependency-name: sqlparse dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements_base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_base.txt b/requirements_base.txt index fb3dac4d..b9773efc 100644 --- a/requirements_base.txt +++ b/requirements_base.txt @@ -50,7 +50,7 @@ pytz==2020.4 raven==6.10.0 reportlab==3.6.1 six==1.15.0 -sqlparse==0.4.1 +sqlparse==0.4.2 #tolerance==0.1.2 -e git+https://github.com/WPI-LNL/tolerance@335211c974e5e1d85289e1a8745ce01794b05225#egg=tolerance xhtml2pdf==0.2.5 From faead9a0fb1e38abfe22fccc55ead7e62b6d06e7 Mon Sep 17 00:00:00 2001 From: Cara Salter Date: Fri, 10 Sep 2021 18:15:39 +0000 Subject: [PATCH 12/14] Add text to indicate a test event MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This presents an initial solution to #432, by adding the text "[TEST EVENT]" to the end of every event marked as a Test Event in the database. Also considered was adding the unicode "⚠" character with a tooltip. This was not added due to the potential for misinterpreting the symbol and for accessibility on mobile. --- site_tmpl/events.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/site_tmpl/events.html b/site_tmpl/events.html index 12ddf4f6..3d922c71 100644 --- a/site_tmpl/events.html +++ b/site_tmpl/events.html @@ -116,7 +116,8 @@

{{h2}}

{% for field in cols %} {% if field.name == 'event_name' %} {{ event.event_name|public_field }} + href="{% url "events:detail" event.id %}">{{ event.event_name|public_field }} + {% if event.test_event %} [TEST EVENT] {% endif %} {% elif field.name == 'org' %} From eb0ace0c6da6f173663c17e7bcca0aa3ca61f821 Mon Sep 17 00:00:00 2001 From: Cara Salter Date: Sat, 11 Sep 2021 16:53:56 +0000 Subject: [PATCH 13/14] Add "Test Event" badges to test events --- site_tmpl/events.html | 2 +- site_tmpl/mywo.html | 4 +++- site_tmpl/uglydetail.html | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/site_tmpl/events.html b/site_tmpl/events.html index 3d922c71..5f5bab59 100644 --- a/site_tmpl/events.html +++ b/site_tmpl/events.html @@ -117,7 +117,7 @@

{{h2}}

{% if field.name == 'event_name' %} {{ event.event_name|public_field }} - {% if event.test_event %} [TEST EVENT] {% endif %} + {% if event.test_event %} Test Event {% endif %} {% elif field.name == 'org' %} diff --git a/site_tmpl/mywo.html b/site_tmpl/mywo.html index af3d7b53..ca16d431 100644 --- a/site_tmpl/mywo.html +++ b/site_tmpl/mywo.html @@ -18,7 +18,9 @@

My Previous Work Orders

{{ org }} {% for event in events %} - {{event.event_name|public_field}} + {{event.event_name|public_field}} + {% if event.test_event %}Test Event{% endif %} + {{event.datetime_end|timesince}} ago {{event.location}} {{ event.status }} diff --git a/site_tmpl/uglydetail.html b/site_tmpl/uglydetail.html index 5111dac1..ac92e735 100644 --- a/site_tmpl/uglydetail.html +++ b/site_tmpl/uglydetail.html @@ -10,6 +10,7 @@

{% endif %} {{ event }} +

From 1bbd89e909d2a3567268ed3ecd6cf342b97ef024 Mon Sep 17 00:00:00 2001 From: Cara Salter Date: Mon, 25 Oct 2021 03:28:09 +0000 Subject: [PATCH 14/14] projection: Make standard list link to profile If the user is not able to edit PITs, the list will link to the projectionist's profile. --- site_tmpl/projectionlist_detail.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site_tmpl/projectionlist_detail.html b/site_tmpl/projectionlist_detail.html index e7f0dfb4..cd3a1df7 100644 --- a/site_tmpl/projectionlist_detail.html +++ b/site_tmpl/projectionlist_detail.html @@ -33,7 +33,7 @@

{{ h2 }}

Licensed {% endif %} {% else %} - {{ user }} + {{ user }} {% if user in licensed_users %} Licensed {% endif %}