Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DB Slack Channel Management #899

Open
wants to merge 35 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
6bfc9eb
Split Slack API into two files
benjamin-antupit Jan 1, 2025
31d43ed
Add Slack Channel List and WIP Channel Detail
benjamin-antupit Jan 1, 2025
53bf10c
Improve channel details
benjamin-antupit Jan 2, 2025
8edc098
Update slack_channel_list.html
benjamin-antupit Jan 2, 2025
b65e54d
Add env var for SLACK_BASE_URL
benjamin-antupit Jan 2, 2025
2cea5fd
Add channel migration
benjamin-antupit Jan 2, 2025
1410fca
move slack pages to /db/slack/*
benjamin-antupit Jan 3, 2025
c96bf73
Fix broken string formatting
benjamin-antupit Jan 3, 2025
9f8b9f2
Update models.py
benjamin-antupit Jan 3, 2025
f9dfec6
Move channels for groups to channel model
benjamin-antupit Jan 3, 2025
3e0b5cf
Update appname in tests
benjamin-antupit Jan 3, 2025
6444eb9
Update urls.py
benjamin-antupit Jan 3, 2025
d7073dc
Auto-create Channel object on channel creation
benjamin-antupit Jan 3, 2025
55a48e6
Re-add user to required channel if they leave
benjamin-antupit Jan 3, 2025
f4e9bf5
Add slack_channel relations to BaseEvent and Org
benjamin-antupit Jan 4, 2025
baa1c44
Add event/org fields in channel configuration form
benjamin-antupit Jan 4, 2025
254ad80
Add Slack Channel Directory
benjamin-antupit Jan 4, 2025
9fd6c8c
Cleanup: Update links, date formatting
benjamin-antupit Jan 4, 2025
2c8e28a
Update api.py
benjamin-antupit Jan 5, 2025
74911a3
Add users in required_channel group to channel
benjamin-antupit Jan 5, 2025
273a828
Add CCs to event channel when CCs added or event channel added
benjamin-antupit Jan 5, 2025
e5ae6a5
Update forms.py
benjamin-antupit Jan 6, 2025
673b03e
Update forms.py
benjamin-antupit Jan 6, 2025
26e370b
Move slack user lookup logic to `slack/api.py`
benjamin-antupit Jan 6, 2025
3a87bc0
Remove exceptions for slack channel user add
benjamin-antupit Jan 6, 2025
27453d4
Create Event Channel modal
benjamin-antupit Jan 6, 2025
51157cb
Switch to add_ccs_to_channel()
benjamin-antupit Jan 6, 2025
57033c6
Update models.py
benjamin-antupit Jan 6, 2025
caade8c
Add event checkbox to notify slack channel w/edits
benjamin-antupit Jan 6, 2025
841ed40
Refactor add group to channel for DRY
benjamin-antupit Jan 6, 2025
f69f43b
Add exec to new event channels
benjamin-antupit Jan 6, 2025
9ec0dde
Merge branch 'master' into db-slack-management
benjamin-antupit Jan 6, 2025
79c7b2b
Set channel topic once DB identifies a channel
benjamin-antupit Jan 6, 2025
cabc70a
Set topic when linking or creating event channel
benjamin-antupit Jan 6, 2025
1eab710
Update settings.py
benjamin-antupit Jan 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions accounts/lookups.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from ajax_select import LookupChannel
from django.contrib.auth import get_user_model
from django.db.models import Q
from django.contrib.auth.models import Group

from . import ldap
from . import graph
Expand Down Expand Up @@ -37,6 +38,25 @@ def format_item_display(self, obj):
(self.get_result(obj), ", ".join(map(str, obj.groups.all())))
return '&nbsp;<strong>%s</strong>' % self.get_result(obj)

class GroupLookup(LookupChannel):
model = Group

def check_auth(self, request):
if request.user.groups.filter(Q(name="Alumni") | Q(name="Active") | Q(name="Officer")).exists():
return True

def get_query(self, q, request, search_ldap=True):
qs = Q()
for term in q.split():
qs &= Q(name__icontains=term)
return Group.objects.filter(qs).distinct().all()

def format_match(self, obj):
return self.format_item_display(obj)

def format_item_display(self, obj):
return '&nbsp;<strong>%s</strong> <i>(%s users)</i>' % \
(self.get_result(obj), obj.user_set.count())

class OfficerLookup(LookupChannel):
model = get_user_model()
Expand Down
6 changes: 3 additions & 3 deletions accounts/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,12 @@ def form_valid(self, form):
officer_info.save()

# Kick user from exec chat in Slack (if applicable)
slack_user = lookup_user(self.object.email)
slack_user = lookup_user(self.object)
if slack_user and exec_group in oldgroups:
user_kick(settings.SLACK_TARGET_EXEC, slack_user)
elif exec_group not in oldgroups:
# Attempt to add user to exec chat in Slack
slack_user = lookup_user(self.object.email)
slack_user = lookup_user(self.object)
if slack_user:
user_add(settings.SLACK_TARGET_EXEC, slack_user)

Expand Down Expand Up @@ -127,7 +127,7 @@ def get_context_data(self, **kwargs):
context['hour_total'] = u.hours.aggregate(hours=Sum('hours'))
context['ccs'] = u.ccinstances.select_related('event').all()

slack_id = lookup_user(u.email)
slack_id = lookup_user(u)
slack_profile = user_profile(slack_id)
if slack_profile['ok']:
context['slack_id'] = slack_id
Expand Down
30 changes: 25 additions & 5 deletions events/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from events.widgets import ValueSelectField
from helpers.form_text import markdown_at_msgs
from helpers.util import curry_class
from slack.models import Channel

LIGHT_EXTRAS = Extra.objects.exclude(disappear=True).filter(category__name="Lighting")
LIGHT_EXTRAS_ID_NAME = LIGHT_EXTRAS.values_list('id', 'name')
Expand Down Expand Up @@ -178,6 +179,7 @@ def __init__(self, request_user, *args, **kwargs):
'Contact',
Field('name', css_class=read_only),
'exec_email',
'slack_channel',
'address',
Field('phone', css_class="bfh-phone", data_format="(ddd) ddd dddd"),
),
Expand Down Expand Up @@ -211,10 +213,16 @@ def clean_worktag(self):
raise ValidationError('What you entered is not a valid worktag. Here are some examples of what a worktag '
'looks like: 1234-CC, 123-AG')
return self.cleaned_data['worktag']

def clean_slack_channel(self):
if self.cleaned_data['slack_channel'] is not None and self.cleaned_data['slack_channel'] != '':
return Channel.get_or_create(self.cleaned_data['slack_channel'])
return None


class Meta:
model = Organization
fields = ('name', 'exec_email', 'address', 'phone', 'associated_orgs', 'personal',
fields = ('name', 'exec_email', 'address', 'phone', 'associated_orgs', 'personal', 'slack_channel',
'workday_fund', 'worktag', 'user_in_charge', 'associated_users', 'notes', 'delinquent')

# associated_orgs = make_ajax_field(Organization,'associated_orgs','Orgs',plugin_options = {'minLength':2})
Expand All @@ -224,6 +232,7 @@ class Meta:
associated_users = AutoCompleteSelectMultipleField('Users', required=False)
worktag = forms.CharField(required=False, help_text='Ends in -AG, -CC, -GF, -GR, or -DE')
notes = forms.CharField(widget=EasyMDEEditor(), label="Internal Notes", required=False)
slack_channel = forms.CharField(required=False, label="Slack Channel", validators=[Channel.validate_field], help_text="Slack Channel ID, i.e. C4HB02R6H")

class FieldAccess:
def __init__(self):
Expand Down Expand Up @@ -426,6 +435,7 @@ def __init__(self, request_user, *args, **kwargs):
'billed_in_bulk',
'sensitive',
'test_event',
'notifications_in_slack_channel',
active=True
),
Tab(
Expand Down Expand Up @@ -548,7 +558,7 @@ class Meta:
fields = ('event_name', 'event_status', 'location', 'lnl_contact', 'description', 'internal_notes', 'billing_org', 'billed_in_bulk', 'contact',
'org', 'datetime_setup_complete', 'datetime_start', 'datetime_end', 'lighting', 'lighting_reqs',
'sound', 'sound_reqs', 'projection', 'proj_reqs', 'otherservices', 'otherservice_reqs', 'sensitive',
'test_event')
'test_event', 'notifications_in_slack_channel')
widgets = {
'description': EasyMDEEditor(),
'internal_notes': EasyMDEEditor(),
Expand Down Expand Up @@ -584,6 +594,7 @@ def __init__(self, request_user, *args, **kwargs):
'location',
'lnl_contact',
'reference_code',
'slack_channel',
Field('description'),
DynamicFieldContainer('internal_notes'),
HTML('<div style="width: 50%">'),
Expand All @@ -593,6 +604,7 @@ def __init__(self, request_user, *args, **kwargs):
'billed_in_bulk',
'sensitive',
'test_event',
'notifications_in_slack_channel',
'entered_into_workday',
'send_survey',
active=True
Expand Down Expand Up @@ -717,9 +729,9 @@ class Meta:
model = Event2019
fields = ('event_name', 'event_status', 'location', 'lnl_contact', 'description', 'internal_notes', 'billing_org',
'billed_in_bulk', 'contact', 'org', 'datetime_setup_complete', 'datetime_start',
'datetime_end', 'sensitive', 'test_event',
'datetime_end', 'sensitive', 'test_event','notifications_in_slack_channel',
'entered_into_workday', 'send_survey', 'max_crew','cancelled_reason',
'reference_code')
'reference_code','slack_channel',)
widgets = {
'description': EasyMDEEditor(),
'internal_notes': EasyMDEEditor(),
Expand All @@ -745,6 +757,12 @@ class Meta:
# `2022-ABNXQQ`
reference_code =forms.CharField(validators=[RegexValidator(regex=r"[0-9]{4}-[A-Z]{6}")],
required = False)
slack_channel = forms.CharField(required=False, label="Slack Channel", validators=[Channel.validate_field], help_text="Slack Channel ID, i.e. C4HB02R6H")

def clean_slack_channel(self):
if self.cleaned_data['slack_channel'] is not None and self.cleaned_data['slack_channel'] != '':
return Channel.get_or_create(self.cleaned_data['slack_channel'])
return None


class EventReviewForm(forms.ModelForm):
Expand Down Expand Up @@ -1359,7 +1377,7 @@ def clean(self):
return cleaned_data

def save(self, commit=True):
obj = super(CCIForm, self).save(commit=False)
obj:EventCCInstance = super(CCIForm, self).save(commit=False)
try:
obj.category
except Category.DoesNotExist:
Expand All @@ -1370,6 +1388,8 @@ def save(self, commit=True):
obj.event = self.event
if commit:
obj.save()
if obj.event.slack_channel:
obj.event.slack_channel.add_ccs_to_channel()
return obj

class Meta:
Expand Down
23 changes: 22 additions & 1 deletion events/lookups.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,30 @@
from django.db.models import Q
from django.utils.html import escape

from events.models import Organization
from events.models import BaseEvent, Organization


class EventLookup(LookupChannel):
model = BaseEvent

def check_auth(self, request):
return request.user.is_authenticated

def get_query(self, q, request):
if request.user.groups.filter(name="Officer").exists():
return BaseEvent.objects.filter(Q(event_name__icontains=q) | Q(description__icontains=q)).distinct()
return BaseEvent.objects.filter(Q(event_name__icontains=q) | Q(description__icontains=q)).\
filter(approved=True, closed=False, cancelled=False, test_event=False, sensitive=False).distinct()

def get_result(self, obj):
return obj.event_name

def format_match(self, obj):
return self.format_item_display(obj)

def format_item_display(self, obj: BaseEvent):
return '&nbsp;<strong>%s</strong> (%s)' % (escape(obj.event_name), escape(obj.status))

class OrgLookup(LookupChannel):
model = Organization

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Generated by Django 4.2.13 on 2025-01-03 08:11

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):
dependencies = [
("slack", "0005_channel_allowed_groups_channel_required_groups"),
("events", "0013_alter_baseevent_polymorphic_ctype_and_more"),
]

operations = [
migrations.AddField(
model_name="baseevent",
name="slack_channel",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="event",
to="slack.channel",
),
),
migrations.AddField(
model_name="organization",
name="slack_channel",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="organization",
to="slack.channel",
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Generated by Django 4.2.13 on 2025-01-06 09:44

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):
dependencies = [
("slack", "0005_channel_allowed_groups_channel_required_groups"),
("events", "0014_baseevent_slack_channel_organization_slack_channel"),
]

operations = [
migrations.AddField(
model_name="baseevent",
name="notifications_in_slack_channel",
field=models.BooleanField(
default=True,
help_text="Check to send event edited notifications to the event slack channel",
verbose_name="Send edit notifications to Slack channel",
),
),
migrations.AlterField(
model_name="baseevent",
name="slack_channel",
field=models.ForeignKey(
blank=True,
help_text="Slack Channel ID, i.e. C4HB02R6H",
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="event",
to="slack.channel",
),
),
migrations.AlterField(
model_name="organization",
name="slack_channel",
field=models.ForeignKey(
blank=True,
help_text="Slack Channel ID, i.e. C4HB02R6H",
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="organization",
to="slack.channel",
),
),
]
5 changes: 5 additions & 0 deletions events/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,10 @@ class BaseEvent(PolymorphicModel):
billed_in_bulk = models.BooleanField(default=False, db_index=True, help_text="Check if billing of this event will be deferred so that it can be combined with other events in a single invoice")
sensitive = models.BooleanField(default=False, help_text="Nobody besides those directly involved should know about this event")
test_event = models.BooleanField(default=False, help_text="Check to lower the VP's blood pressure after they see the short-notice S4/L4")
notifications_in_slack_channel = models.BooleanField(default=True, help_text="Check to send event edited notifications to the event slack channel", verbose_name="Send edit notifications to Slack channel")

slack_channel = models.ForeignKey('slack.Channel', on_delete=models.PROTECT, null=True, blank=True, related_name='event', help_text="Slack Channel ID, i.e. C4HB02R6H")

# Status Indicators
approved = models.BooleanField(default=False)
approved_on = models.DateTimeField(null=True, blank=True)
Expand Down Expand Up @@ -1181,6 +1184,8 @@ class Organization(models.Model):

locked = models.BooleanField(default=False, blank=True)

slack_channel = models.ForeignKey('slack.Channel', on_delete=models.PROTECT, null=True, blank=True, related_name='organization', help_text="Slack Channel ID, i.e. C4HB02R6H")

def __str__(self):
return self.name

Expand Down
2 changes: 1 addition & 1 deletion events/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def email_cc_notification(sender, instance, created, raw=False, **kwargs):
e.send()
if 'Slack Notification' in str(prefs.cc_add_subscriptions).split(', '):
blocks = cc_add_notification(instance)
slack_user = lookup_user(instance.crew_chief.email)
slack_user = lookup_user(instance.crew_chief)
if slack_user:
message = "You've been added as a crew chief for the event %s." % instance.event.event_name
slack_post(slack_user, text=message, content=blocks)
Expand Down
1 change: 1 addition & 0 deletions events/urls/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def generate_date_patterns(func, name):
re_path(r'^close/(?P<id>[0-9a-f]+)/$', flow_views.close, name="close"),
re_path(r'^cancel/(?P<id>[0-9a-f]+)/$', flow_views.cancel, name="cancel"),
re_path(r'^reopen/(?P<id>[0-9a-f]+)/$', flow_views.reopen, name="reopen"),
re_path(r'^createchannel/(?P<id>[0-9a-f]+)/$', flow_views.createchannel, name="create-channel"),
re_path(r'^crew/(?P<id>[0-9a-f]+)/$', flow_views.assigncrew, name="add-crew"),
re_path(r'^rmcrew/(?P<id>[0-9a-f]+)/(?P<user>[0-9a-f]+)/$',
flow_views.rmcrew, name="remove-crew"),
Expand Down
Loading
Loading