diff --git a/organizations/admin.py b/organizations/admin.py index fcf97dfc..5e767b5d 100644 --- a/organizations/admin.py +++ b/organizations/admin.py @@ -4,15 +4,13 @@ from operator import itemgetter from ckeditor.widgets import CKEditorWidget - from django.contrib import admin - -from django.db.models import Q - +from django.db.models import Q, Count +from django.template.defaultfilters import striptags from django.utils.encoding import smart_text +from django.utils.translation import ugettext_lazy as _ from . import models -from organizations.models import Facility DEFAULT_FILTER_ROLES = (models.Membership.Roles.ADMIN, models.Membership.Roles.MANAGER) @@ -27,7 +25,7 @@ def get_memberships_by_role(membership_queryset): return memberships_by_role -def get_cached_memberships(user): +def get_cached_memberships(user, roles=DEFAULT_FILTER_ROLES): user_memberships = getattr(user, '__memberships', None) if not user_memberships: user_memberships = { @@ -36,7 +34,14 @@ def get_cached_memberships(user): user.account.organization_set), } setattr(user, '__memberships', user_memberships) - return user_memberships + + user_orgs = list(itertools.chain.from_iterable( + user_memberships['organizations'][role] for role in roles)) + + user_facilities = list(itertools.chain.from_iterable( + user_memberships['facilities'][role] for role in roles)) + + return user_orgs, user_facilities def filter_queryset_by_membership(qs, user, @@ -50,17 +55,12 @@ def filter_queryset_by_membership(qs, user, if user.is_superuser: return qs - user_memberships = get_cached_memberships(user) - user_orgs = itertools.chain.from_iterable( - user_memberships['organizations'][role] for role in roles) - - user_facilities = itertools.chain.from_iterable( - user_memberships['facilities'][role] for role in roles) + user_orgs, user_facilities = get_cached_memberships(user, roles) if qs.model == models.Organization: - return qs.filter(pk__in=user_orgs) + qs = qs.filter(pk__in=user_orgs) elif qs.model == models.Facility: - return qs.filter( + qs = qs.filter( Q(pk__in=user_facilities) | Q(organization_id__in=user_orgs) ) @@ -69,24 +69,66 @@ def filter_queryset_by_membership(qs, user, facility_filter_fk = 'facility' if organization_filter_fk: - return qs.filter(**{organization_filter_fk + '_id__in': user_orgs}) - else: - return qs.filter( + qs = qs.filter(**{organization_filter_fk + '_id__in': user_orgs}) + elif facility_filter_fk: + qs = qs.filter( Q(**{facility_filter_fk + '_id__in': user_facilities}) | Q(**{facility_filter_fk + '__organization_id__in': user_orgs}) ) + return qs class MembershipFilteredAdmin(admin.ModelAdmin): facility_filter_fk = 'facility' widgets = None + def get_readonly_fields(self, request, obj=None): + readonly = super(MembershipFilteredAdmin, self).get_readonly_fields( + request=request, obj=obj) + if request.user.is_superuser: + return readonly + else: + if not ('facility' in readonly and 'organization' in readonly): + user_orgs, user_facilities = get_cached_memberships( + request.user) + if len(user_facilities) <= 1 and hasattr(obj, 'facility') \ + and 'facility' not in readonly: + readonly += ('facility',) + if len(user_orgs) <= 1 and hasattr(obj, 'organization') \ + and 'organization' not in readonly: + readonly += ('organization',) + return readonly + + def get_list_display(self, request): + list_display = list( + super(MembershipFilteredAdmin, self).get_list_display(request)) + if request.user.is_superuser: + return list_display + if 'facility' in list_display or 'organization' in list_display: + user_orgs, user_facilities = get_cached_memberships(request.user) + if len(user_facilities) <= 1 and 'facility' in list_display: + list_display.remove('facility') + if len(user_orgs) <= 1 and 'organization' in list_display: + list_display.remove('organization') + return list_display + + def get_list_display_links(self, request, list_display): + list_display_links = list( + super(MembershipFilteredAdmin, self).get_list_display_links(request, + list_display)) + return filter(lambda i: i in list_display, list_display_links) + + def get_edit_link(self, obj): + return _(u'edit') + + get_edit_link.short_description = _(u'edit') + def get_form(self, request, obj=None, **kwargs): form = super(MembershipFilteredAdmin, self).get_form( request, obj, widgets=self.widgets, **kwargs) if 'facility' in form.base_fields: - facilities = Facility.objects.all() + facilities = models.Facility.objects.all() user_facilities = filter_queryset_by_membership(facilities, request.user) if len(user_facilities) == 1: @@ -113,6 +155,11 @@ def get_field_queryset(self, db, db_field, request): class MembershipFilteredTabularInline(admin.TabularInline): facility_filter_fk = 'facility' + widgets = None + + def get_formset(self, request, obj=None, **kwargs): + return super(MembershipFilteredTabularInline, self).get_formset( + request, obj, widgets=self.widgets, **kwargs) def get_queryset(self, request): qs = super(MembershipFilteredTabularInline, self).get_queryset(request) @@ -133,17 +180,38 @@ def get_field_queryset(self, db, db_field, request): class MembershipFieldListFilter(admin.RelatedFieldListFilter): def field_choices(self, field, request, model_admin): - qs = filter_queryset_by_membership(field.rel.to.objects.all(), - request.user) + query = field.rel.to.objects.all() + query = query.annotate(usage_count=Count(field.related_query_name())) + query = query.exclude(usage_count=0) + qs = filter_queryset_by_membership(query, request.user) return [(x._get_pk_val(), smart_text(x)) for x in qs] @admin.register(models.Organization) class OrganizationAdmin(MembershipFilteredAdmin): + + def get_short_description(self, obj): + return striptags(obj.short_description) + + get_short_description.short_description = _(u'short description') + get_short_description.allow_tags = True + + def get_description(self, obj): + return striptags(obj.description) + + get_description.short_description = _(u'description') + get_description.allow_tags = True + + def get_contact_info(self, obj): + return striptags(obj.contact_info) + + get_contact_info.short_description = _(u'contact info') + get_contact_info.allow_tags = True + list_display = ( 'name', 'short_description', - 'description', + 'get_description', 'contact_info', 'address', ) @@ -158,12 +226,30 @@ class OrganizationAdmin(MembershipFilteredAdmin): @admin.register(models.Facility) class FacilityAdmin(MembershipFilteredAdmin): + def get_short_description(self, obj): + return striptags(obj.short_description) + + get_short_description.short_description = _(u'short description') + get_short_description.allow_tags = True + + def get_description(self, obj): + return striptags(obj.description) + + get_description.short_description = _(u'description') + get_description.allow_tags = True + + def get_contact_info(self, obj): + return striptags(obj.contact_info) + + get_contact_info.short_description = _(u'contact info') + get_contact_info.allow_tags = True + list_display = ( 'organization', 'name', - 'short_description', - 'description', - 'contact_info', + 'get_short_description', + 'get_description', + 'get_contact_info', 'place', 'address', 'zip_code', @@ -176,7 +262,6 @@ class FacilityAdmin(MembershipFilteredAdmin): ) raw_id_fields = ('members',) search_fields = ('name',) - radio_fields = {"organization": admin.VERTICAL} widgets = { 'short_description': CKEditorWidget(), 'description': CKEditorWidget(), @@ -212,10 +297,16 @@ class FacilityMembershipAdmin(MembershipFilteredAdmin): @admin.register(models.Workplace) class WorkplaceAdmin(MembershipFilteredAdmin): + def get_description(self, obj): + return striptags(obj.description) + + get_description.short_description = _(u'description') + get_description.allow_tags = True + list_display = ( 'facility', 'name', - 'description' + 'get_description' ) list_filter = ( ('facility', MembershipFieldListFilter), @@ -230,10 +321,16 @@ class WorkplaceAdmin(MembershipFilteredAdmin): @admin.register(models.Task) class TaskAdmin(MembershipFilteredAdmin): + def get_description(self, obj): + return striptags(obj.description) + + get_description.short_description = _(u'description') + get_description.allow_tags = True + list_display = ( 'facility', 'name', - 'description' + 'get_description' ) list_filter = ( ('facility', MembershipFieldListFilter), diff --git a/organizations/models.py b/organizations/models.py index 85129408..3a96efc2 100644 --- a/organizations/models.py +++ b/organizations/models.py @@ -56,7 +56,7 @@ class Facility(models.Model): description = models.TextField(verbose_name=_(u'description')) # anything one needs to know on how to contact the facility - contact_info = models.TextField(verbose_name=_(u'description')) + contact_info = models.TextField(verbose_name=_(u'contact info')) # users associated with this facility # ie. members, admins, admins diff --git a/scheduler/admin.py b/scheduler/admin.py index 59dc10c7..8ffbe22a 100644 --- a/scheduler/admin.py +++ b/scheduler/admin.py @@ -1,10 +1,13 @@ # coding: utf-8 from django.contrib import admin from django.db.models import Count +from django.utils.translation import ugettext_lazy as _ from . import models -from organizations.admin import MembershipFilteredAdmin, \ +from organizations.admin import ( + MembershipFilteredAdmin, MembershipFieldListFilter +) @admin.register(models.Shift) @@ -22,15 +25,24 @@ def get_queryset(self, request): def get_volunteer_count(self, obj): return obj.volunteer_count + get_volunteer_count.short_description = _(u'number of volunteers') + get_volunteer_count.admin_order_field = 'volunteer_count' + def get_volunteer_names(self, obj): def _format_username(user): full_name = user.get_full_name() + username = u'{}
{}'.format(user.username, + user.email) if full_name: - return u'{} ("{}")'.format(full_name, user.username) - return u'"{}"'.format(user.username) + username = u'{} / {}'.format(full_name, username) + return u'
  • {}
  • '.format(username) + + return u"".format( + u"\n".join(_format_username(volunteer.user) for volunteer in + obj.helpers.all())) - return u", ".join(_format_username(volunteer.user) for volunteer in - obj.helpers.all()) + get_volunteer_names.short_description = _(u'volunteers') + get_volunteer_names.allow_tags = True list_display = ( 'task', diff --git a/scheduletemplates/admin.py b/scheduletemplates/admin.py index 4efd044a..8ed4f2c0 100644 --- a/scheduletemplates/admin.py +++ b/scheduletemplates/admin.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- from datetime import timedelta, datetime, time +from django.utils import formats from django import forms -from django.conf.global_settings import SHORT_DATE_FORMAT from django.conf.urls import url from django.contrib import admin, messages from django.core.urlresolvers import reverse from django.db.models import Min, Count, Sum -from django.forms import DateInput +from django.forms import DateInput, TimeInput from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404, redirect from django.template.response import TemplateResponse @@ -15,19 +15,53 @@ from django.utils import timezone from django.utils.translation import ugettext_lazy as _, ungettext_lazy -from .models import ScheduleTemplate, ShiftTemplate +from . import models from organizations.admin import (MembershipFilteredAdmin, MembershipFilteredTabularInline, - MembershipFieldListFilter) -from scheduler.models import Shift + MembershipFieldListFilter, + filter_queryset_by_membership) +from scheduler import models as scheduler_models + + +class ShiftTemplateForm(forms.ModelForm): + time_formats = formats.get_format('TIME_INPUT_FORMATS') + ('%H', '%H%M') + + class Meta: + model = models.ShiftTemplate + fields = '__all__' + + starting_time = forms.TimeField(label=_(u'starting time'), + widget=TimeInput, + input_formats=time_formats) + ending_time = forms.TimeField(label=_(u'ending time'), + widget=TimeInput, + input_formats=time_formats) class ShiftTemplateInline(MembershipFilteredTabularInline): - model = ShiftTemplate + model = models.ShiftTemplate + min_num = 0 extra = 0 facility_filter_fk = 'schedule_template__facility' template = 'admin/scheduletemplates/shifttemplate/shift_template_inline.html' + form = ShiftTemplateForm + + +JQUERYUI_FORMAT_MAPPING = { + '%Y': 'yy', + '%y': 'y', + '%m': 'mm', + '%b': 'M', + '%d': 'dd', + '%B': 'MM', +} + + +def translate_date_format(format_string, mappings=JQUERYUI_FORMAT_MAPPING): + for k, v in mappings.iteritems(): + format_string = format_string.replace(k, v) + return format_string class ApplyTemplateForm(forms.Form): @@ -39,7 +73,11 @@ class ApplyTemplateForm(forms.Form): """ apply_for_date = forms.DateField(widget=DateInput) - date_format = SHORT_DATE_FORMAT + + def __init__(self, *args, **kwargs): + super(ApplyTemplateForm, self).__init__(*args, **kwargs) + self.js_date_format = translate_date_format( + formats.get_format_lazy('DATE_INPUT_FORMATS')[0]) class Media: css = { @@ -53,7 +91,7 @@ class Media: ) -@admin.register(ScheduleTemplate) +@admin.register(models.ScheduleTemplate) class ScheduleTemplateAdmin(MembershipFilteredAdmin): inlines = [ShiftTemplateInline] list_display = ( @@ -68,7 +106,6 @@ class ScheduleTemplateAdmin(MembershipFilteredAdmin): ) search_fields = ('name',) list_select_related = True - radio_fields = {"facility": admin.VERTICAL} def response_change(self, request, obj): if "_save_and_apply" in request.POST: @@ -91,8 +128,8 @@ def apply_schedule_template(self, request, pk): shift_templates = schedule_template.shift_templates.all() context = dict(self.admin_site.each_context(request)) - context[ - "opts"] = self.model._meta # Needed for admin template breadcrumbs + context["opts"] = self.model._meta + # Needed for admin template breadcrumbs # Phase 1 if request.method == 'GET': @@ -128,7 +165,7 @@ def apply_schedule_template(self, request, pk): # Phase 2: display a preview of whole day if request.POST.get('preview'): - existing_shifts = Shift.objects.filter( + existing_shifts = scheduler_models.Shift.objects.filter( facility=schedule_template.facility) existing_shifts = existing_shifts.on_shiftdate(apply_date) existing_shifts = existing_shifts.select_related('task', @@ -150,7 +187,7 @@ def apply_schedule_template(self, request, pk): # returns (task, workplace, start_time and is_template) # to make combined list sortable def __shift_key(shift): - is_template = isinstance(shift, ShiftTemplate) + is_template = isinstance(shift, models.ShiftTemplate) task = shift.task.id if shift.task else 0 workplace = shift.workplace.id if shift.workplace else 0 shift_start = shift.starting_time @@ -184,7 +221,7 @@ def __shift_key(shift): for template in selected_shift_templates: starting_time = datetime.combine(apply_date, template.starting_time) - Shift.objects.create( + scheduler_models.Shift.objects.create( facility=template.schedule_template.facility, starting_time=starting_time, ending_time=starting_time + template.duration, @@ -253,17 +290,18 @@ def get_latest_ending_time(self, obj): '-ending_time')[ 0:1].get() return latest_shift.localized_display_ending_time - except ShiftTemplate.DoesNotExist: + except models.ShiftTemplate.DoesNotExist: pass return None get_latest_ending_time.short_description = _('to') -@admin.register(ShiftTemplate) +@admin.register(models.ShiftTemplate) class ShiftTemplateAdmin(MembershipFilteredAdmin): + form = ShiftTemplateForm list_display = ( - u'id', + 'get_edit_link', 'schedule_template', 'slots', 'task', @@ -271,15 +309,26 @@ class ShiftTemplateAdmin(MembershipFilteredAdmin): 'starting_time', 'ending_time', 'days', - ) - # list_filter = ('schedule_template__facility', 'task', 'workplace') - list_filter = ( ('schedule_template__facility', MembershipFieldListFilter), + ('schedule_template', MembershipFieldListFilter), ('task', MembershipFieldListFilter), ('workplace', MembershipFieldListFilter), ) - + search_fields = ( + 'schedule_template__name', + 'task__name', + 'workplace__name', + 'schedule_template__facility__name', + 'schedule_template__facility__organization__name', + ) facility_filter_fk = 'schedule_template__facility' - radio_fields = {"schedule_template": admin.VERTICAL} + + def get_field_queryset(self, db, db_field, request): + qs = super(ShiftTemplateAdmin, self).get_field_queryset( + db, db_field, request) + if db_field.rel.to == models.ScheduleTemplate: + qs = qs or db_field.rel.to.objects.all() + qs = filter_queryset_by_membership(qs, request.user) + return qs