| 1 | # Create your views here. |
|---|
| 2 | |
|---|
| 3 | import collections |
|---|
| 4 | import datetime |
|---|
| 5 | |
|---|
| 6 | import groups.models |
|---|
| 7 | |
|---|
| 8 | from django.contrib.auth.decorators import user_passes_test, login_required, permission_required |
|---|
| 9 | from django.contrib.contenttypes.models import ContentType |
|---|
| 10 | from django.core.exceptions import PermissionDenied |
|---|
| 11 | from django.views.generic import ListView, DetailView |
|---|
| 12 | from django.shortcuts import render_to_response, get_object_or_404 |
|---|
| 13 | from django.template import RequestContext |
|---|
| 14 | from django.template import Context, Template |
|---|
| 15 | from django.template.loader import get_template |
|---|
| 16 | from django.http import Http404, HttpResponseRedirect |
|---|
| 17 | from django.core.urlresolvers import reverse |
|---|
| 18 | from django.core.mail import EmailMessage, mail_admins |
|---|
| 19 | from django.forms import Form |
|---|
| 20 | from django.forms import ModelForm |
|---|
| 21 | from django.forms import ModelChoiceField |
|---|
| 22 | from django.db import connection |
|---|
| 23 | from django.db.models import Q |
|---|
| 24 | |
|---|
| 25 | import form_utils.forms |
|---|
| 26 | import reversion.models |
|---|
| 27 | import django_filters |
|---|
| 28 | |
|---|
| 29 | from util.db_form_utils import StaticWidget |
|---|
| 30 | |
|---|
| 31 | |
|---|
| 32 | def view_homepage(request, ): |
|---|
| 33 | users_groups = [] |
|---|
| 34 | groupmsg = "" |
|---|
| 35 | has_perms = [] |
|---|
| 36 | if request.user.is_authenticated(): |
|---|
| 37 | username = request.user.username |
|---|
| 38 | current_officers = groups.models.OfficeHolder.current_holders.filter(person=username) |
|---|
| 39 | users_groups = groups.models.Group.objects.filter(officeholder__in=current_officers).distinct() |
|---|
| 40 | |
|---|
| 41 | perms = [] |
|---|
| 42 | perms.extend(groups.models.Group._meta.permissions) |
|---|
| 43 | perms.extend(groups.models.GroupNote._meta.permissions) |
|---|
| 44 | perms += ( |
|---|
| 45 | ('change_group', 'Change arbitrary group information', ), |
|---|
| 46 | ) |
|---|
| 47 | for perm_name, perm_desc in perms: |
|---|
| 48 | if request.user.has_perm('groups.%s' % (perm_name, )): |
|---|
| 49 | has_perms.append((perm_name, perm_desc, )) |
|---|
| 50 | |
|---|
| 51 | context = { |
|---|
| 52 | 'groups': users_groups, |
|---|
| 53 | 'groupmsg': groupmsg, |
|---|
| 54 | 'has_perms': has_perms, |
|---|
| 55 | 'pagename': 'homepage', |
|---|
| 56 | } |
|---|
| 57 | return render_to_response('index.html', context, context_instance=RequestContext(request), ) |
|---|
| 58 | |
|---|
| 59 | |
|---|
| 60 | class GroupChangeMainForm(form_utils.forms.BetterModelForm): |
|---|
| 61 | def __init__(self, *args, **kwargs): |
|---|
| 62 | change_restricted = False |
|---|
| 63 | if 'change_restricted' in kwargs: |
|---|
| 64 | change_restricted = kwargs['change_restricted'] |
|---|
| 65 | del kwargs['change_restricted'] |
|---|
| 66 | super(GroupChangeMainForm, self).__init__(*args, **kwargs) |
|---|
| 67 | restricted_fields = list(self.nobody_fields) |
|---|
| 68 | if change_restricted: |
|---|
| 69 | restricted_fields.extend(self.exec_only_fields) |
|---|
| 70 | for field_name in restricted_fields: |
|---|
| 71 | formfield = self.fields[field_name] |
|---|
| 72 | value = getattr(self.instance, field_name) |
|---|
| 73 | StaticWidget.replace_widget(formfield, value) |
|---|
| 74 | |
|---|
| 75 | exec_only_fields = [ |
|---|
| 76 | 'name', 'abbreviation', |
|---|
| 77 | 'group_status', 'group_class', |
|---|
| 78 | 'group_funding', 'main_account_id', 'funding_account_id', |
|---|
| 79 | ] |
|---|
| 80 | nobody_fields = [ |
|---|
| 81 | 'recognition_date', |
|---|
| 82 | ] |
|---|
| 83 | |
|---|
| 84 | class Meta: |
|---|
| 85 | fieldsets = [ |
|---|
| 86 | ('basic', { |
|---|
| 87 | 'legend': 'Basic Information', |
|---|
| 88 | 'fields': ['name', 'abbreviation', 'activity_category', 'description', ], |
|---|
| 89 | }), |
|---|
| 90 | ('size', { |
|---|
| 91 | 'legend':'Membership Numbers', |
|---|
| 92 | 'fields': ['num_undergrads', 'num_grads', 'num_community', 'num_other',], |
|---|
| 93 | }), |
|---|
| 94 | ('contact', { |
|---|
| 95 | 'legend': 'Contact Information', |
|---|
| 96 | 'fields': ['website_url', 'meeting_times', 'officer_email', 'group_email', ], |
|---|
| 97 | }), |
|---|
| 98 | ('recognition', { |
|---|
| 99 | 'legend': 'Recognition', |
|---|
| 100 | 'fields': ['group_status', 'group_class', 'recognition_date', ], |
|---|
| 101 | }), |
|---|
| 102 | ('financial', { |
|---|
| 103 | 'legend': 'Financial Information', |
|---|
| 104 | 'fields': ['group_funding', 'main_account_id', 'funding_account_id', ], |
|---|
| 105 | }), |
|---|
| 106 | ('more-info', { |
|---|
| 107 | 'legend': 'Additional Information', |
|---|
| 108 | 'fields': ['constitution_url', 'advisor_name', 'athena_locker', ], |
|---|
| 109 | }), |
|---|
| 110 | ] |
|---|
| 111 | model = groups.models.Group |
|---|
| 112 | |
|---|
| 113 | def manage_main(request, group_id, ): |
|---|
| 114 | group = get_object_or_404(groups.models.Group, pk=group_id) |
|---|
| 115 | |
|---|
| 116 | if not request.user.has_perm('groups.admin_group', group): |
|---|
| 117 | raise PermissionDenied |
|---|
| 118 | change_restricted = True |
|---|
| 119 | if request.user.has_perm('groups.change_group', group): |
|---|
| 120 | change_restricted = False |
|---|
| 121 | |
|---|
| 122 | msg = None |
|---|
| 123 | |
|---|
| 124 | initial = {} |
|---|
| 125 | if request.method == 'POST': # If the form has been submitted... |
|---|
| 126 | # A form bound to the POST data |
|---|
| 127 | form = GroupChangeMainForm( |
|---|
| 128 | request.POST, request.FILES, |
|---|
| 129 | change_restricted=change_restricted, |
|---|
| 130 | instance=group, |
|---|
| 131 | ) |
|---|
| 132 | |
|---|
| 133 | if form.is_valid(): # All validation rules pass |
|---|
| 134 | request_obj = form.save(commit=False) |
|---|
| 135 | request_obj.set_updater(request.user) |
|---|
| 136 | request_obj.save() |
|---|
| 137 | form.save_m2m() |
|---|
| 138 | |
|---|
| 139 | # Send email |
|---|
| 140 | #tmpl = get_template('fysm/update_email.txt') |
|---|
| 141 | #ctx = Context({ |
|---|
| 142 | # 'group': group_obj, |
|---|
| 143 | # 'fysm': fysm_obj, |
|---|
| 144 | # 'view_uri': view_uri, |
|---|
| 145 | # 'submitter': request.user, |
|---|
| 146 | # 'request': request, |
|---|
| 147 | # 'sender': "ASA FYSM team", |
|---|
| 148 | #}) |
|---|
| 149 | #body = tmpl.render(ctx) |
|---|
| 150 | #email = EmailMessage( |
|---|
| 151 | # subject='FYSM entry for "%s" updated by "%s"' % ( |
|---|
| 152 | # group_obj.name, |
|---|
| 153 | # request.user, |
|---|
| 154 | # ), |
|---|
| 155 | # body=body, |
|---|
| 156 | # from_email='asa-fysm@mit.edu', |
|---|
| 157 | # to=[group_obj.officer_email, request.user.email, ], |
|---|
| 158 | # bcc=['asa-fysm-submissions@mit.edu', ] |
|---|
| 159 | #) |
|---|
| 160 | #email.send() |
|---|
| 161 | msg = "Thanks for editing!" |
|---|
| 162 | else: |
|---|
| 163 | msg = "Validation failed. See below for details." |
|---|
| 164 | |
|---|
| 165 | else: |
|---|
| 166 | form = GroupChangeMainForm(change_restricted=change_restricted, instance=group, initial=initial, ) # An unbound form |
|---|
| 167 | |
|---|
| 168 | context = { |
|---|
| 169 | 'group': group, |
|---|
| 170 | 'form': form, |
|---|
| 171 | 'msg': msg, |
|---|
| 172 | } |
|---|
| 173 | return render_to_response('groups/group_change_main.html', context, context_instance=RequestContext(request), ) |
|---|
| 174 | |
|---|
| 175 | |
|---|
| 176 | class GroupFilter(django_filters.FilterSet): |
|---|
| 177 | name = django_filters.CharFilter(lookup_type='icontains', label="Name contains") |
|---|
| 178 | abbreviation = django_filters.CharFilter(lookup_type='iexact', label="Abbreviation is") |
|---|
| 179 | |
|---|
| 180 | class Meta: |
|---|
| 181 | model = groups.models.Group |
|---|
| 182 | fields = [ |
|---|
| 183 | 'name', |
|---|
| 184 | 'abbreviation', |
|---|
| 185 | 'activity_category', |
|---|
| 186 | 'group_class', |
|---|
| 187 | 'group_status', |
|---|
| 188 | 'group_funding', |
|---|
| 189 | ] |
|---|
| 190 | |
|---|
| 191 | |
|---|
| 192 | class GroupListView(ListView): |
|---|
| 193 | model = groups.models.Group |
|---|
| 194 | template_object_name = 'group' |
|---|
| 195 | |
|---|
| 196 | def get(self, *args, **kwargs): |
|---|
| 197 | qs = super(GroupListView, self).get_queryset() |
|---|
| 198 | self.filterset = GroupFilter(self.request.GET, qs) |
|---|
| 199 | return super(GroupListView, self).get(*args, **kwargs) |
|---|
| 200 | |
|---|
| 201 | def get_queryset(self, ): |
|---|
| 202 | qs = self.filterset.qs |
|---|
| 203 | return qs |
|---|
| 204 | |
|---|
| 205 | def get_context_data(self, **kwargs): |
|---|
| 206 | context = super(GroupListView, self).get_context_data(**kwargs) |
|---|
| 207 | # Add in the publisher |
|---|
| 208 | context['pagename'] = 'groups' |
|---|
| 209 | context['filter'] = self.filterset |
|---|
| 210 | return context |
|---|
| 211 | |
|---|
| 212 | |
|---|
| 213 | class GroupDetailView(DetailView): |
|---|
| 214 | context_object_name = "group" |
|---|
| 215 | model = groups.models.Group |
|---|
| 216 | def get_context_data(self, **kwargs): |
|---|
| 217 | # Call the base implementation first to get a context |
|---|
| 218 | context = super(GroupDetailView, self).get_context_data(**kwargs) |
|---|
| 219 | group = context['group'] |
|---|
| 220 | |
|---|
| 221 | # Indicate whether this person should be able to see "private" info |
|---|
| 222 | context['viewpriv'] = self.request.user.has_perm('groups.view_group_private_info', group) |
|---|
| 223 | context['adminpriv'] = self.request.user.has_perm('groups.admin_group', group) |
|---|
| 224 | context['notes'] = group.viewable_notes(self.request.user) |
|---|
| 225 | |
|---|
| 226 | # People involved in the group |
|---|
| 227 | just_roles = groups.models.OfficerRole.objects.all() |
|---|
| 228 | if context['viewpriv'] or self.request.user.has_perm('groups.view_signatories'): |
|---|
| 229 | # Can see the non-public stuff |
|---|
| 230 | pass |
|---|
| 231 | else: |
|---|
| 232 | just_roles = just_roles.filter(publicly_visible=True) |
|---|
| 233 | roles = [] |
|---|
| 234 | for role in just_roles: |
|---|
| 235 | roles.append((role.display_name, role, group.officers(role=role), )) |
|---|
| 236 | context['roles'] = roles |
|---|
| 237 | |
|---|
| 238 | return context |
|---|
| 239 | |
|---|
| 240 | |
|---|
| 241 | class GroupHistoryView(ListView): |
|---|
| 242 | context_object_name = "version_list" |
|---|
| 243 | template_name = "groups/group_version.html" |
|---|
| 244 | |
|---|
| 245 | def get_queryset(self): |
|---|
| 246 | history_entries = None |
|---|
| 247 | if 'group' in self.kwargs: |
|---|
| 248 | group = get_object_or_404(groups.models.Group, pk=self.kwargs['group']) |
|---|
| 249 | history_entries = reversion.models.Version.objects.get_for_object(group) |
|---|
| 250 | else: |
|---|
| 251 | history_entries = reversion.models.Version.objects.all() |
|---|
| 252 | group_content_type = ContentType.objects.get_for_model(groups.models.Group) |
|---|
| 253 | history_entries = history_entries.filter(content_type=group_content_type) |
|---|
| 254 | length = len(history_entries) |
|---|
| 255 | if length > 150: |
|---|
| 256 | history_entries = history_entries[length-100:] |
|---|
| 257 | return history_entries |
|---|
| 258 | |
|---|
| 259 | def get_context_data(self, **kwargs): |
|---|
| 260 | context = super(GroupHistoryView, self).get_context_data(**kwargs) |
|---|
| 261 | if 'group' in self.kwargs: |
|---|
| 262 | group = get_object_or_404(groups.models.Group, pk=self.kwargs['group']) |
|---|
| 263 | context['title'] = "History for %s" % (group.name, ) |
|---|
| 264 | else: |
|---|
| 265 | context['title'] = "Recent Changes" |
|---|
| 266 | return context |
|---|
| 267 | |
|---|
| 268 | |
|---|
| 269 | def load_officers(group, ): |
|---|
| 270 | officers = group.officers() |
|---|
| 271 | people = list(set([ officer.person for officer in officers ])) |
|---|
| 272 | roles = groups.models.OfficerRole.objects.all() |
|---|
| 273 | |
|---|
| 274 | officers_map = {} |
|---|
| 275 | for officer in officers: |
|---|
| 276 | officers_map[(officer.person, officer.role)] = officer |
|---|
| 277 | |
|---|
| 278 | return people, roles, officers_map |
|---|
| 279 | |
|---|
| 280 | def manage_officers(request, group_id, ): |
|---|
| 281 | group = get_object_or_404(groups.models.Group, pk=group_id) |
|---|
| 282 | |
|---|
| 283 | if not request.user.has_perm('groups.admin_group', group): |
|---|
| 284 | raise PermissionDenied |
|---|
| 285 | |
|---|
| 286 | max_new = 4 |
|---|
| 287 | |
|---|
| 288 | people, roles, officers_map = load_officers(group) |
|---|
| 289 | |
|---|
| 290 | msgs = [] |
|---|
| 291 | changes = [] |
|---|
| 292 | edited = False |
|---|
| 293 | kept = 0 |
|---|
| 294 | kept_not = 0 |
|---|
| 295 | if request.method == 'POST': # If the form has been submitted |
|---|
| 296 | edited = True |
|---|
| 297 | |
|---|
| 298 | new_people = {} |
|---|
| 299 | moira_accounts = {} |
|---|
| 300 | for i in range(max_new): |
|---|
| 301 | key = "extra.%d" % (i, ) |
|---|
| 302 | if key in request.POST and request.POST[key] != "": |
|---|
| 303 | username = request.POST[key] |
|---|
| 304 | try: |
|---|
| 305 | moira_accounts[username] = groups.models.AthenaMoiraAccount.active_accounts.get(username=username) |
|---|
| 306 | new_people[i] = username |
|---|
| 307 | except groups.models.AthenaMoiraAccount.DoesNotExist: |
|---|
| 308 | msgs.append('Athena account "%s" appears not to exist. Changes involving them have been ignored.' % (username, )) |
|---|
| 309 | for person in people: |
|---|
| 310 | try: |
|---|
| 311 | moira_accounts[person] = groups.models.AthenaMoiraAccount.active_accounts.get(username=person) |
|---|
| 312 | except groups.models.AthenaMoiraAccount.DoesNotExist: |
|---|
| 313 | msgs.append('Athena account "%s" appears not to exist. They can not be added to new roles. You should remove them from any roles they hold, if you have not already.' % (person, )) |
|---|
| 314 | for role in roles: |
|---|
| 315 | key = "holders.%s" % (role.slug, ) |
|---|
| 316 | new_holders = set() |
|---|
| 317 | if key in request.POST: |
|---|
| 318 | new_holders = set(request.POST.getlist(key, )) |
|---|
| 319 | if len(new_holders) > role.max_count: |
|---|
| 320 | msgs.append("You selected %d people for %s; only %d are allowed. No changes to %s have been carried out in this update." % |
|---|
| 321 | (len(new_holders), role.display_name, role.max_count, role.display_name, ) |
|---|
| 322 | ) |
|---|
| 323 | else: |
|---|
| 324 | for person in people: |
|---|
| 325 | if person in new_holders: |
|---|
| 326 | if (person, role) in officers_map: |
|---|
| 327 | if role.require_student and not moira_accounts[person].is_student(): |
|---|
| 328 | msgs.append('Only students can have the %s role, and %s does not appear to be a student. (If this is not the case, please contact us.) You should replace this person ASAP.' % (role, person, )) |
|---|
| 329 | #changes.append(("Kept", "yellow", person, role)) |
|---|
| 330 | kept += 1 |
|---|
| 331 | else: |
|---|
| 332 | if person not in moira_accounts: |
|---|
| 333 | msgs.append('Could not add nonexistent Athena account "%s" as %s.' % (person, role, )) |
|---|
| 334 | elif role.require_student and not moira_accounts[person].is_student(): |
|---|
| 335 | msgs.append('Only students can have the %s role, and %s does not appear to be a student. (If this is not the case, please contact us.)' % (role, person, )) |
|---|
| 336 | else: |
|---|
| 337 | holder = groups.models.OfficeHolder(person=person, role=role, group=group,) |
|---|
| 338 | holder.save() |
|---|
| 339 | changes.append(("Added", "green", person, role)) |
|---|
| 340 | else: |
|---|
| 341 | if (person, role) in officers_map: |
|---|
| 342 | officers_map[(person, role)].expire() |
|---|
| 343 | changes.append(("Removed", "red", person, role)) |
|---|
| 344 | else: |
|---|
| 345 | kept_not += 1 |
|---|
| 346 | pass |
|---|
| 347 | for i in range(max_new): |
|---|
| 348 | if "extra.%d" % (i, ) in new_holders: |
|---|
| 349 | if i in new_people: |
|---|
| 350 | person = new_people[i] |
|---|
| 351 | if role.require_student and not moira_accounts[person].is_student(): |
|---|
| 352 | msgs.append('Only students can have the %s role, and %s does not appear to be a student.' % (role, person, )) |
|---|
| 353 | else: |
|---|
| 354 | holder = groups.models.OfficeHolder(person=person, role=role, group=group,) |
|---|
| 355 | holder.save() |
|---|
| 356 | changes.append(("Added", "green", person, role)) |
|---|
| 357 | |
|---|
| 358 | # mark as changed and reload the data |
|---|
| 359 | if changes: |
|---|
| 360 | group.set_updater(request.user) |
|---|
| 361 | group.save() |
|---|
| 362 | people, roles, officers_map = load_officers(group) |
|---|
| 363 | |
|---|
| 364 | officers_data = [] |
|---|
| 365 | for person in people: |
|---|
| 366 | role_list = [] |
|---|
| 367 | for role in roles: |
|---|
| 368 | if (person, role) in officers_map: |
|---|
| 369 | role_list.append((role, True)) |
|---|
| 370 | else: |
|---|
| 371 | role_list.append((role, False)) |
|---|
| 372 | officers_data.append((False, person, role_list)) |
|---|
| 373 | null_role_list = [(role, False) for role in roles] |
|---|
| 374 | for i in range(max_new): |
|---|
| 375 | officers_data.append((True, "extra.%d" % (i, ), null_role_list)) |
|---|
| 376 | |
|---|
| 377 | context = { |
|---|
| 378 | 'group': group, |
|---|
| 379 | 'roles': roles, |
|---|
| 380 | 'people': people, |
|---|
| 381 | 'officers': officers_data, |
|---|
| 382 | 'edited': edited, |
|---|
| 383 | 'changes': changes, |
|---|
| 384 | 'kept': kept, |
|---|
| 385 | 'kept_not': kept_not, |
|---|
| 386 | 'msgs': msgs, |
|---|
| 387 | } |
|---|
| 388 | return render_to_response('groups/group_change_officers.html', context, context_instance=RequestContext(request), ) |
|---|
| 389 | |
|---|
| 390 | @permission_required('groups.view_signatories') |
|---|
| 391 | def view_signatories(request, ): |
|---|
| 392 | # TODO: |
|---|
| 393 | # * limit which columns (roles) get displayed |
|---|
| 394 | # * limit which rows get displayed (maybe) by category or something |
|---|
| 395 | # This might want to wait for the generic reporting infrastructure, since |
|---|
| 396 | # I'd imagine some of it can be reused. |
|---|
| 397 | |
|---|
| 398 | officers = groups.models.OfficeHolder.objects.filter(start_time__lte=datetime.datetime.now(), end_time__gte=datetime.datetime.now()) |
|---|
| 399 | officers = officers.select_related(depth=1) |
|---|
| 400 | all_groups = groups.models.Group.objects.all() |
|---|
| 401 | roles = groups.models.OfficerRole.objects.all() |
|---|
| 402 | officers_map = collections.defaultdict(lambda: collections.defaultdict(set)) |
|---|
| 403 | for officer in officers: |
|---|
| 404 | officers_map[officer.group][officer.role].add(officer.person) |
|---|
| 405 | officers_data = [] |
|---|
| 406 | for group in all_groups: |
|---|
| 407 | role_list = [] |
|---|
| 408 | for role in roles: |
|---|
| 409 | role_list.append(officers_map[group][role]) |
|---|
| 410 | officers_data.append((group, role_list)) |
|---|
| 411 | |
|---|
| 412 | context = { |
|---|
| 413 | 'roles': roles, |
|---|
| 414 | 'officers': officers_data, |
|---|
| 415 | } |
|---|
| 416 | return render_to_response('groups/groups_signatories.html', context, context_instance=RequestContext(request), ) |
|---|