Ejemplo n.º 1
0
class OrganizationAdmin(admin.ModelAdmin):
    inlines = (OrganizationInitiativeInline, )

    list_display = ('name', 'website', 'created')
    list_filter = (
        ('initiatives__theme', admin.RelatedOnlyFieldListFilter),
        ('initiatives__location', admin.RelatedOnlyFieldListFilter),
    )
    fields = ('name', 'website', 'description', 'logo')
    search_fields = ('name', )
    export_fields = [
        ('name', 'name'),
        ('website', 'website'),
        ('created', 'created'),
    ]

    formfield_overrides = {
        models.URLField: {
            'widget': SecureAdminURLFieldWidget()
        },
    }

    def get_inline_instances(self, request, obj=None):
        """ Override get_inline_instances so that add form do not show inlines """
        if not obj:
            return []
        return super(OrganizationAdmin,
                     self).get_inline_instances(request, obj)

    actions = (export_as_csv_action(fields=export_fields), )
Ejemplo n.º 2
0
class StepInline(NestedStackedInline, SortableStackedInline):
    model = Step
    extra = 0
    formfield_overrides = {
        models.URLField: {
            'widget': SecureAdminURLFieldWidget()
        },
    }
Ejemplo n.º 3
0
class CategoryContentInline(SortableStackedInline, TranslatableStackedInline):
    formfield_overrides = {
        models.TextField: {
            'widget': forms.Textarea(attrs={
                'rows': 3,
                'cols': 80
            })
        },
        models.URLField: {
            'widget': SecureAdminURLFieldWidget()
        },
    }

    model = CategoryContent
    extra = 0
    max_num = 3
Ejemplo n.º 4
0
class FundraiserAdmin(admin.ModelAdmin):
    list_display = ('title', 'owner_link', 'amount_donated_override',
                    'amount_override', 'deadline')
    raw_id_fields = ('project', 'owner')

    search_fields = ('title', 'project__title')

    readonly_fields = ('project_link', 'owner_link')

    formfield_overrides = {
        models.URLField: {
            'widget': SecureAdminURLFieldWidget()
        },
    }

    def amount_override(self, obj):
        return obj.amount

    amount_override.short_description = 'amount asked'

    def amount_donated_override(self, obj):
        return obj.amount_donated

    amount_donated_override.short_description = 'amount donated'

    def project_link(self, obj):
        object = obj.project
        url = reverse('admin:{0}_{1}_change'.format(object._meta.app_label,
                                                    object._meta.model_name),
                      args=[object.id])

        return format_html(u"<a href='{}'>{}</a>", str(url), object.title)

    project_link.short_description = _('Project link')

    def owner_link(self, obj):
        object = obj.owner
        url = reverse('admin:{0}_{1}_change'.format(object._meta.app_label,
                                                    object._meta.model_name),
                      args=[object.id])

        return format_html(u"<a href='{}'>{}</a>", str(url), object.email)

    owner_link.short_description = 'initiator'
Ejemplo n.º 5
0
class SurveyAdmin(admin.ModelAdmin):
    model = Survey
    readonly_fields = ('title', 'link', 'created', 'updated')
    fields = ('remote_id', 'last_synced', 'active') + readonly_fields
    list_display = ('title', 'last_synced', 'response_count')

    inlines = [QuestionAdminInline]

    actions = ['synchronize_surveys']

    formfield_overrides = {
        models.URLField: {
            'widget': SecureAdminURLFieldWidget()
        },
    }

    def response_count(self, obj):
        return obj.response_set.count()

    def synchronize_surveys(self, request, queryset):
        for survey in queryset:
            survey.synchronize()
Ejemplo n.º 6
0
class SuggestionAdmin(admin.ModelAdmin):
    list_display = ('title', 'destination', 'deadline', 'created', 'updated',
                    'status')
    list_filter = ('status', 'destination', ExpiredFilter)

    readonly_fields = ('created', 'updated', 'expired')

    formfield_overrides = {
        models.URLField: {
            'widget': SecureAdminURLFieldWidget()
        },
    }

    def updated(self, obj):
        return obj.updated

    def created(self, obj):
        return obj.created

    def expired(self, obj):
        return obj.expired

    raw_id_fields = ('project', )

    readonly_fields = ('project_link', )

    def project_link(self, obj):
        if obj.project:
            url = reverse('admin:{0}_{1}_change'.format(
                obj.project._meta.app_label, obj.project._meta.model_name),
                          args=[obj.project.id])
            return format_html(u"<a href='{}' target='_blank'>{}</a>", url,
                               obj.project.title)
        return u"(None)"

    project_link.short_description = _('Project link')
Ejemplo n.º 7
0
class MediaWallpostAdmin(BaseWallpostAdmin):
    readonly_fields = ('ip_address', 'created', 'deleted', 'view_online',
                       'gallery', 'donation', 'share_with_facebook',
                       'share_with_twitter', 'share_with_linkedin',
                       'email_followers')
    fields = readonly_fields + ('text', 'author', 'editor', 'pinned')
    raw_id_fields = ('author', 'editor')
    list_display = ('created', 'view_online', 'get_text', 'thumbnail',
                    'author', 'deleted')
    search_fields = ('text', 'author__first_name', 'author__last_name')
    exclude = ('object_id', 'content_type')

    extra_fields = ('gallery', )

    ordering = ('-created', )
    inlines = (MediaWallpostPhotoInline, ReactionInline)

    formfield_overrides = {
        models.URLField: {
            'widget': SecureAdminURLFieldWidget()
        },
    }

    def get_text(self, obj):
        if len(obj.text) > 150:
            return format_html(u'<span title="{}">{} [...]</span>', obj.text,
                               obj.text[:145])

    def thumbnail(self, obj):
        data = {}
        if obj.video_url:
            data['video_url'] = obj.video_url
            if 'youtube.com' in obj.video_url:
                try:
                    urlparts = urllib.parse.urlparse(obj.video_url)
                    data['youtubeid'] = urllib.parse.parse_qs(
                        urlparts.query)['v'][0]
                except (KeyError, ValueError, IndexError):
                    pass

        photos = MediaWallpostPhoto.objects.filter(mediawallpost=obj)
        data['count'] = len(photos)
        data['remains'] = max(0, data['count'] - 1)

        if len(photos):
            if photos[0].photo:
                data['firstimage'] = get_thumbnail(photos[0].photo,
                                                   "120x120",
                                                   crop="center").url
                data['firstimage_url'] = photos[0].photo.url

        return mark_safe(
            render_to_string("admin/wallposts/preview_thumbnail.html", data))

    def gallery(self, obj):
        data = {}
        data['images'] = [
            dict(full=p.photo.url,
                 thumb=get_thumbnail(p.photo, "120x120", crop="center").url)
            for p in obj.photos.all()
        ]

        return mark_safe(
            render_to_string("admin/wallposts/mediawallpost_gallery.html",
                             data))

    def get_queryset(self, request):
        """ The Admin needs to show all the Reactions. """
        return self.model.objects_with_deleted.all()
Ejemplo n.º 8
0
class MemberAdmin(UserAdmin):
    raw_id_fields = ('partner_organization', )

    formfield_overrides = {
        models.URLField: {'widget': SecureAdminURLFieldWidget()},
    }

    @property
    def standard_fieldsets(self):

        standard_fieldsets = [
            [_("Main"), {'fields': [
                'remote_id',
                'email',
                'first_name',
                'last_name',
                'username',
                'phone_number',
                'reset_password',
                'resend_welcome_link',
                'last_login',
                'date_joined',
                'deleted',
                'is_co_financer',
                'can_pledge',
                'partner_organization',
                'campaign_notifications',
                'newsletter',
                'primary_language',
            ]}],
            [_("Profile"),
             {'fields': [
                 'picture',
                 'about_me',
                 'favourite_themes',
                 'skills'
             ]}],
            [_('Engagement'),
             {'fields': [
                 'projects_managed',
                 'tasks',
                 'donations',
                 'following'
             ]}],
        ]

        if Location.objects.count():
            standard_fieldsets[1][1]['fields'].append('location')

        if 'Pledge' not in (item['name'] for item in properties.PAYMENT_METHODS):
            standard_fieldsets[0][1]['fields'].remove('can_pledge')

        if CustomMemberFieldSettings.objects.count():
            extra = (_('Extra fields'), {
                'fields': [field.slug for field in CustomMemberFieldSettings.objects.all()]
            })

            standard_fieldsets.append(extra)

        return tuple(standard_fieldsets)

    staff_fieldsets = (
        (_('Permissions'), {'fields': ('is_active', 'is_staff', 'can_pledge', 'groups')}),
    )

    superuser_fieldsets = (
        (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser', 'can_pledge', 'groups')}),
    )

    add_fieldsets = (
        (None, {'classes': ('wide',),
                'fields': ('first_name', 'last_name', 'email',
                           'is_active', 'is_staff', 'is_superuser', 'groups')}
         ),
    )

    readonly_fields = ('date_joined', 'last_login',
                       'updated', 'deleted', 'login_as_user',
                       'reset_password', 'resend_welcome_link',
                       'projects_managed', 'tasks', 'donations', 'following')

    export_fields = (
        ('username', 'username'),
        ('email', 'email'),
        ('remote_id', 'remote_id'),
        ('first_name', 'first_name'),
        ('last_name', 'last name'),
        ('date_joined', 'date joined'),
        ('is_initiator', 'is initiator'),
        ('is_supporter', 'is supporter'),
        ('amount_donated', 'amount donated'),
        ('is_volunteer', 'is volunteer'),
        ('time_spent', 'time spent'),
    )

    actions = (export_as_csv_action(fields=export_fields),)

    form = MemberChangeForm
    add_form = MemberCreationForm

    list_filter = (
        'is_active',
        'newsletter',
        ('favourite_themes', UnionFieldListFilter),
        ('skills', UnionFieldListFilter),
        ('groups', UnionFieldListFilter)
    )
    list_display = ('email', 'first_name', 'last_name', 'is_staff',
                    'date_joined', 'is_active', 'login_as_user')
    ordering = ('-date_joined', 'email',)

    inlines = (UserAddressInline, )

    def projects_managed(self, obj):
        url = reverse('admin:projects_project_changelist')
        completed = Project.objects.filter(owner=obj, status__slug__in=['done-complete', 'done-incomplete']).count()
        campaign = Project.objects.filter(owner=obj, status__slug__in=['campaign', 'voting-running']).count()
        submitted = Project.objects.filter(owner=obj, status__slug='plan-submitted').count()
        plan = Project.objects.filter(owner=obj, status__slug__in=['plan-new', 'plan-needs-work']).count()
        links = []
        if completed:
            links.append('<a href="{}?owner={}&status_filter=8%2C9">{} {}</a>'.format(
                url, obj.id, completed, _('completed')
            ))
        if campaign:
            links.append('<a href="{}?owner={}&status_filter=11%2C5">{} {}</a>'.format(
                url, obj.id, campaign, _('campaigning')
            ))
        if plan:
            links.append('<a href="{}?owner={}&status_filter=1%2C3">{} {}</a>'.format(
                url, obj.id, plan, _('plan')
            ))
        if submitted:
            links.append('<a href="{}?owner={}&status_filter=2">{} {}</a>'.format(
                url, obj.id, submitted, _('submitted')
            ))
        return format_html(', '.join(links) or _('None'))
    projects_managed.short_description = _('Projects Managed')

    def tasks(self, obj):
        url = reverse('admin:tasks_task_changelist')
        owner = Task.objects.filter(author=obj, status__in=['open', 'full', 'running', 'realised']).count()
        applied = Task.objects.filter(members__member=obj, members__status__in=['applied', 'accepted']).count()
        realized = Task.objects.filter(members__member=obj, members__status__in=['realized']).count()
        links = []
        if owner:
            links.append('<a href="{}?author={}">{} {}</a>'.format(
                url, obj.id, owner, _('created')
            ))
        if applied:
            links.append(
                '<a href="{}?members__member_id={}&members__status[]=applied'
                '&members__status[]=accepted">{} {}</a>'.format(url, obj.id, applied, _('applied'))
            )
        if realized:
            links.append('<a href="{}?members__member_id={}">{} {}</a>'.format(
                url, obj.id, realized, _('realised')
            ))
        return format_html(', '.join(links) or _('None'))
    tasks.short_description = _('Tasks')

    def donations(self, obj):
        url = reverse('admin:donations_donation_changelist')
        donations = Donation.objects.filter(order__status__in=['success', 'pending'], order__user=obj).count()
        return format_html('<a href="{}?order__user_id={}">{} {}</a>', url, obj.id, donations, _('donations'))
    donations.short_description = _('Donations')

    def following(self, obj):
        url = reverse('admin:bb_follow_follow_changelist')
        follow_count = Follow.objects.filter(user=obj).count()
        return format_html('<a href="{}?user_id={}">{} objects</a>', url, obj.id, follow_count)
    following.short_description = _('Following')

    def reset_password(self, obj):
        reset_form_url = reverse('admin:auth_user_password_change', args=(obj.id, ))
        reset_mail_url = reverse('admin:auth_user_password_reset_mail', kwargs={'user_id': obj.id})
        properties.set_tenant(connection.tenant)

        return format_html(
            "<a href='{}'>{}</a>  | <a href='{}'>{}</a>",
            reset_form_url, _("Reset password form"),
            reset_mail_url, _("Send reset password mail")
        )

    def resend_welcome_link(self, obj):
        welcome_mail_url = reverse('admin:auth_user_resend_welcome_mail', kwargs={'user_id': obj.id})
        return format_html(
            "<a href='{}'>{}</a>",
            welcome_mail_url, _("Resend welcome email"),
        )

    def login_as_user(self, obj):
        return format_html(
            u"<a href='/login/user/{}'>{}</a>",
            obj.id,
            _('Login as user')
        )

    def change_view(self, request, *args, **kwargs):
        # for superuser
        try:
            if request.user.is_superuser:
                self.fieldsets = self.standard_fieldsets + self.superuser_fieldsets
            else:
                self.fieldsets = self.standard_fieldsets + self.staff_fieldsets

            response = UserAdmin.change_view(self, request, *args, **kwargs)
        finally:
            # Reset fieldsets to its original value
            self.fieldsets = self.standard_fieldsets

        return response

    def __init__(self, *args, **kwargs):
        super(MemberAdmin, self).__init__(*args, **kwargs)

        self.list_display = (
            'email', 'first_name', 'last_name', 'is_staff', 'date_joined',
            'is_active', 'login_as_link')

    def get_inline_instances(self, request, obj=None):
        """ Override get_inline_instances so that the add form does not show inlines """
        if not obj:
            return []
        return super(MemberAdmin, self).get_inline_instances(request, obj)

    def get_urls(self):
        urls = super(MemberAdmin, self).get_urls()

        extra_urls = [
            url(r'^login-as/(?P<user_id>\d+)/$', self.admin_site.admin_view(self.login_as_redirect)),
            url(r'^password-reset/(?P<user_id>\d+)/$',
                self.send_password_reset_mail,
                name='auth_user_password_reset_mail'
                ),
            url(r'^resend_welcome_email/(?P<user_id>\d+)/$',
                self.resend_welcome_email,
                name='auth_user_resend_welcome_mail'
                )
        ]
        return extra_urls + urls

    def send_password_reset_mail(self, request, user_id):
        if not request.user.has_perm('members.change_member'):
            return HttpResponseForbidden('Not allowed to change user')

        user = Member.objects.get(pk=user_id)

        context = {
            'email': user.email,
            'site': tenant_url(),
            'site_name': tenant_url(),
            'uid': int_to_base36(user.pk),
            'user': user,
            'token': default_token_generator.make_token(user),
        }
        subject = loader.render_to_string('bb_accounts/password_reset_subject.txt', context)
        subject = ''.join(subject.splitlines())
        send_mail(
            template_name='bb_accounts/password_reset_email',
            to=user,
            subject=subject,
            **context
        )
        message = _('User {name} will receive an email to reset password.').format(name=user.full_name)
        self.message_user(request, message)
        return HttpResponseRedirect(reverse('admin:members_member_change', args=(user.id, )))

    def resend_welcome_email(self, request, user_id):
        if not request.user.has_perm('members.change_member'):
            return HttpResponseForbidden('Not allowed to change user')

        user = Member.objects.get(pk=user_id)
        send_welcome_mail(user)
        message = _('User {name} will receive an welcome email.').format(name=user.full_name)
        self.message_user(request, message)

        return HttpResponseRedirect(reverse('admin:members_member_change', args=(user.id, )))

    def login_as_redirect(self, *args, **kwargs):
        user = Member.objects.get(id=kwargs.get('user_id', None))
        url = "/login-with/{0}".format(user.get_jwt_token())

        return HttpResponseRedirect(url)

    def login_as_link(self, obj):
        return format_html(
            u"<a target='_blank' href='{}members/member/login-as/{}/'>{}</a>",
            reverse('admin:index'), obj.pk, _('Login as user')
        )
    login_as_link.short_description = _('Login as link')

    def has_delete_permission(self, request, obj=None):
        return False
Ejemplo n.º 9
0
class SlideAdmin(admin.ModelAdmin):
    list_display = ('title', 'sequence', 'status', 'modification_date',
                    'language')
    list_filter = ('status', 'language')
    date_hierarchy = 'publication_date'
    search_fields = ('slug', 'title')
    actions = ['make_published']
    model = Slide
    ordering = ('language', 'sequence', 'title')

    formfield_overrides = {
        models.URLField: {
            'widget': SecureAdminURLFieldWidget()
        },
    }

    fieldsets = (
        (None, {
            'fields': ('language', 'sequence', 'tab_text'),
        }),
        (_('Contents'), {
            'fields': ('title', 'body', 'image', 'background_image',
                       'video_url', 'link_text', 'link_url', 'style'),
        }),
        (_('Publication settings'), {
            'fields': ('status', 'publication_date', 'publication_end_date'),
        }),
    )

    radio_fields = {
        'status': admin.HORIZONTAL,
        'language': admin.HORIZONTAL,
    }

    def get_base_object(self, pk):
        pk = long(pk)
        if pk:
            return Slide.objects.get(pk=pk)
        else:
            return Slide()

    def _get_formset_objects(self, formset):
        all_objects = []

        def dummy_save_base(*args, **kwargs):
            pass

        # Based on BaseModelFormSet.save_existing_objects()
        # +  BaseModelFormSet.save_new_objects()
        for form in formset.initial_forms + formset.extra_forms:
            if formset.can_delete and formset._should_delete_form(form):
                continue

            if not form.is_valid():
                object = form.instance  # Keep old data
                # TODO: merge validated fields into object.
                # Before Django 1.5 that means manually constructing the
                # values as form.cleaned_data is removed.
            else:
                object = form.save(commit=False)
                # Disable actual saving code.
                object.save_base = dummy_save_base
                # Trigger any pre-save code (e.g. fetch OEmbedItem,
                # render CodeItem)
                object.save()

            all_objects.append(object)

        return all_objects

    def save_model(self, request, obj, form, change):
        # Automatically store the user in the author field.
        if not change:
            obj.author = request.user

        if not obj.publication_date:
            # auto_now_add makes the field uneditable.
            # default fills the field before the post is written (too early)
            obj.publication_date = now()
        obj.save()

    STATUS_ICONS = {
        Slide.SlideStatus.published: 'icon-yes.gif',
        Slide.SlideStatus.draft: 'icon-unknown.gif',
    }

    def make_published(self, request, queryset):
        rows_updated = queryset.update(status=Slide.PostStatus.published)

        if rows_updated == 1:
            message = "1 entry was marked as published."
        else:
            message = "{0} entries were marked as published.".format(
                rows_updated)
        self.message_user(request, message)

    make_published.short_description = _("Mark selected entries as published")
Ejemplo n.º 10
0
class MemberAdmin(UserAdmin):
    raw_id_fields = ('partner_organization', )

    formfield_overrides = {
        models.URLField: {
            'widget': SecureAdminURLFieldWidget()
        },
    }

    def get_form(self, request, *args, **kwargs):
        Form = super(MemberAdmin, self).get_form(request, *args, **kwargs)
        return functools.partial(Form, current_user=request.user)

    def get_fieldsets(self, request, obj=None):
        if not obj:
            fieldsets = ((None, {
                'classes': ('wide', ),
                'fields': [
                    'first_name', 'last_name', 'email', 'is_active',
                    'is_staff', 'groups'
                ]
            }), )
        else:
            fieldsets = [
                [
                    _("Main"), {
                        'fields': [
                            'email',
                            'remote_id',
                            'first_name',
                            'last_name',
                            'username',
                            'phone_number',
                            'login_as_link',
                            'reset_password',
                            'resend_welcome_link',
                            'last_login',
                            'date_joined',
                            'deleted',
                            'is_co_financer',
                            'can_pledge',
                            'verified',
                            'partner_organization',
                            'campaign_notifications',
                            'newsletter',
                            'primary_language',
                        ]
                    }
                ],
                [
                    _("Profile"), {
                        'fields':
                        ['picture', 'about_me', 'favourite_themes', 'skills']
                    }
                ],
                [
                    _('Permissions'), {
                        'fields':
                        ['is_active', 'is_staff', 'is_superuser', 'groups']
                    }
                ],
                [
                    _('Engagement'), {
                        'fields':
                        ['initiatives', 'events', 'assignments', 'funding']
                    }
                ],
            ]

            if Location.objects.count():
                fieldsets[1][1]['fields'].append('location')

            if SegmentType.objects.filter(is_active=True).count():
                fieldsets[1][1]['fields'].append('segments')

            if 'Pledge' not in (item['name']
                                for item in properties.PAYMENT_METHODS):
                fieldsets[0][1]['fields'].remove('can_pledge')

            if CustomMemberFieldSettings.objects.count():
                extra = (_('Extra fields'), {
                    'fields': [
                        field.slug
                        for field in CustomMemberFieldSettings.objects.all()
                    ]
                })

                fieldsets.append(extra)

        return fieldsets

    def get_readonly_fields(self, request, obj=None):
        readonly_fields = [
            'date_joined', 'last_login', 'updated', 'deleted', 'login_as_link',
            'reset_password', 'resend_welcome_link', 'initiatives', 'events',
            'assignments', 'funding'
        ]

        user_groups = request.user.groups.all()

        if obj and hasattr(obj, 'groups') and not request.user.is_superuser:
            for group in obj.groups.all():
                if group not in user_groups:
                    readonly_fields.append('email')

        if not request.user.is_superuser:
            if obj and obj.is_superuser:
                readonly_fields.append('email')

            readonly_fields.append('is_superuser')

        return readonly_fields

    export_fields = (
        ('username', 'username'),
        ('email', 'email'),
        ('remote_id', 'remote_id'),
        ('first_name', 'first_name'),
        ('last_name', 'last name'),
        ('date_joined', 'date joined'),
        ('is_initiator', 'is initiator'),
        ('is_supporter', 'is supporter'),
        ('is_volunteer', 'is volunteer'),
        ('amount_donated', 'amount donated'),
        ('time_spent', 'time spent'),
        ('subscribed', 'subscribed to matching projects'),
    )

    actions = (export_as_csv_action(fields=export_fields), )

    form = MemberChangeForm
    add_form = MemberCreationForm

    list_filter = ('is_active', 'newsletter', ('favourite_themes',
                                               UnionFieldListFilter),
                   ('skills', UnionFieldListFilter), ('groups',
                                                      UnionFieldListFilter))
    list_display = ('email', 'first_name', 'last_name', 'is_staff',
                    'date_joined', 'is_active', 'login_as_link')
    ordering = (
        '-date_joined',
        'email',
    )

    inlines = (PlaceInline, UserActivityInline)

    def initiatives(self, obj):
        initiatives = []
        initiative_url = reverse('admin:initiatives_initiative_changelist')
        for field in ['owner', 'reviewer', 'promoter', 'activity_manager']:
            if Initiative.objects.filter(
                    status__in=['draft', 'submitted', 'needs_work'],
                    **{
                        field: obj
                    }).count():
                link = initiative_url + '?{}_id={}'.format(field, obj.id)
                initiatives.append(
                    format_html(
                        '<a href="{}">{}</a> draft {}',
                        link,
                        Initiative.objects.filter(
                            status__in=['draft', 'submitted', 'needs_work'],
                            **{
                                field: obj
                            }).count(),
                        field,
                    ))
        if Initiative.objects.filter(status='approved', **{
                field: obj
        }).count():
            link = initiative_url + '?{}_id={}'.format(field, obj.id)
            initiatives.append(
                format_html(
                    '<a href="{}">{}</a> open {}',
                    link,
                    Initiative.objects.filter(status='approved',
                                              **{
                                                  field: obj
                                              }).count(),
                    field,
                ))
        return format_html('<br/>'.join(initiatives)) or _('None')

    initiatives.short_description = _('Initiatives')

    def events(self, obj):
        participants = []
        participant_url = reverse('admin:events_participant_changelist')
        for status in [
                'new', 'succeeded', 'failed', 'withdrawn', 'rejected',
                'no_show'
        ]:
            if Participant.objects.filter(status=status, user=obj).count():
                link = participant_url + '?user_id={}&status={}'.format(
                    obj.id, status)
                participants.append(
                    format_html(
                        '<a href="{}">{}</a> {}',
                        link,
                        Participant.objects.filter(status=status,
                                                   user=obj).count(),
                        status,
                    ))
        return format_html('<br/>'.join(participants)) or _('None')

    events.short_description = _('Event participation')

    def assignments(self, obj):
        applicants = []
        applicant_url = reverse('admin:assignments_applicant_changelist')
        for status in [
                'new', 'accepted', 'active', 'succeeded', 'failed',
                'withdrawn', 'rejected', 'no_show'
        ]:
            if Applicant.objects.filter(status=status, user=obj).count():
                link = applicant_url + '?user_id={}&status={}'.format(
                    obj.id, status)
                applicants.append(
                    format_html(
                        '<a href="{}">{}</a> {}',
                        link,
                        Applicant.objects.filter(status=status,
                                                 user=obj).count(),
                        status,
                    ))
        return format_html('<br/>'.join(applicants)) or _('None')

    def funding(self, obj):
        donations = []
        donation_url = reverse('admin:funding_donation_changelist')
        if Donation.objects.filter(status='succeeded', user=obj).count():
            link = donation_url + '?user_id={}'.format(obj.id)
            donations.append(
                format_html(
                    '<a href="{}">{}</a> donations',
                    link,
                    Donation.objects.filter(status='succeeded',
                                            user=obj).count(),
                ))
        return format_html('<br/>'.join(donations)) or _('None')

    funding.short_description = _('Funding donations')

    def following(self, obj):
        url = reverse('admin:bb_follow_follow_changelist')
        follow_count = Follow.objects.filter(user=obj).count()
        return format_html('<a href="{}?user_id={}">{} objects</a>', url,
                           obj.id, follow_count)

    following.short_description = _('Following')

    def reset_password(self, obj):
        reset_mail_url = reverse('admin:auth_user_password_reset_mail',
                                 kwargs={'pk': obj.id})
        properties.set_tenant(connection.tenant)

        return format_html("<a href='{}'>{}</a>", reset_mail_url,
                           _("Send reset password mail"))

    def resend_welcome_link(self, obj):
        welcome_mail_url = reverse('admin:auth_user_resend_welcome_mail',
                                   kwargs={'pk': obj.id})
        return format_html(
            "<a href='{}'>{}</a>",
            welcome_mail_url,
            _("Resend welcome email"),
        )

    def get_inline_instances(self, request, obj=None):
        """ Override get_inline_instances so that the add form does not show inlines """
        if not obj:
            return []
        return super(MemberAdmin, self).get_inline_instances(request, obj)

    def get_urls(self):
        urls = super(MemberAdmin, self).get_urls()

        extra_urls = [
            url(r'^login-as/(?P<pk>\d+)/$',
                self.admin_site.admin_view(self.login_as),
                name='members_member_login_as'),
            url(r'^password-reset/(?P<pk>\d+)/$',
                self.send_password_reset_mail,
                name='auth_user_password_reset_mail'),
            url(r'^resend_welcome_email/(?P<pk>\d+)/$',
                self.resend_welcome_email,
                name='auth_user_resend_welcome_mail')
        ]
        return extra_urls + urls

    @confirmation_form(SendPasswordResetMailConfirmationForm, Member,
                       'admin/members/password_reset.html')
    def send_password_reset_mail(self, request, user):
        if not request.user.has_perm('members.change_member'):
            return HttpResponseForbidden('Not allowed to change user')

        context = {
            'email': user.email,
            'site': tenant_url(),
            'site_name': tenant_url(),
            'uid': int_to_base36(user.pk),
            'user': user,
            'token': default_token_generator.make_token(user),
        }
        subject = loader.render_to_string(
            'bb_accounts/password_reset_subject.txt', context)
        subject = ''.join(subject.splitlines())
        send_mail(template_name='bb_accounts/password_reset_email',
                  to=user,
                  subject=subject,
                  **context)
        message = _('User {name} will receive an email to reset password.'
                    ).format(name=user.full_name)
        self.message_user(request, message)
        return HttpResponseRedirect(
            reverse('admin:members_member_change', args=(user.id, )))

    @confirmation_form(SendWelcomeMailConfirmationForm, Member,
                       'admin/members/resend_welcome_mail.html')
    def resend_welcome_email(self, request, user):
        if not request.user.has_perm('members.change_member'):
            return HttpResponseForbidden('Not allowed to change user')

        send_welcome_mail(user)

        message = _('User {name} will receive an welcome email.').format(
            name=user.full_name)
        self.message_user(request, message)

        return HttpResponseRedirect(
            reverse('admin:members_member_change', args=(user.id, )))

    @confirmation_form(LoginAsConfirmationForm, Member,
                       'admin/members/login_as.html')
    def login_as(self, request, user):
        template = loader.get_template('utils/login_with.html')
        context = {'token': user.get_jwt_token(), 'link': '/'}
        response = HttpResponse(template.render(context, request),
                                content_type='text/html')
        response['cache-control'] = "no-store, no-cache, private"
        return response

    def login_as_link(self, obj):
        url = reverse('admin:members_member_login_as', args=(obj.pk, ))
        return format_html(u"<a target='_blank' href='{}'>{}</a>", url,
                           _('Login as user'))

    login_as_link.short_description = _('Login as')

    def has_delete_permission(self, request, obj=None):
        if obj and obj.contribution_set.exclude(
                status__in=['deleted', 'failed']).count() == 0:
            return True
        return False
Ejemplo n.º 11
0
class ProjectAdmin(AdminImageMixin, PolymorphicInlineSupportMixin, ImprovedModelForm):
    form = ProjectAdminForm
    date_hierarchy = 'deadline'
    ordering = ('-created',)

    save_as = True
    search_fields = (
        'title', 'owner__first_name', 'owner__last_name', 'owner__email',
        'organization__name', 'organization__contacts__email'
    )
    raw_id_fields = ('owner', 'reviewer', 'task_manager', 'promoter', 'organization',)
    prepopulated_fields = {'slug': ('title',)}

    formfield_overrides = {
        models.URLField: {'widget': SecureAdminURLFieldWidget()},
    }

    class Media:
        css = {
            'all': ('css/admin/wide-actions.css',)
        }
        js = ('admin/js/inline-task-add.js',)

    def get_inline_instances(self, request, obj=None):
        self.inlines = self.all_inlines
        if obj:
            # We need to reload project, or we get an error when changing project type
            project = Project.objects.get(pk=obj.id)
            if project.project_type == 'sourcing':
                self.inlines = self.sourcing_inlines
        elif request.POST.get('project_type', '') == 'sourcing':
            self.inlines = self.sourcing_inlines

        instances = super(ProjectAdmin, self).get_inline_instances(request, obj)
        add_on_inline = ProjectAddOnInline(self.model, self.admin_site)
        if len(add_on_inline.get_child_inline_instances()):
            instances.append(add_on_inline)
        return instances

    all_inlines = (
        ProjectLocationInline,
        ProjectBudgetLineInline,
        RewardInlineAdmin,
        TaskAdminInline,
        ProjectDocumentInline,
        ProjectPhaseLogInline
    )
    sourcing_inlines = (
        ProjectLocationInline,
        ProjectDocumentInline,
        TaskAdminInline,
        ProjectPhaseLogInline
    )

    list_filter = ('country__subregion__region', )

    export_fields = [
        ('title', 'title'),
        ('owner', 'owner'),
        ('owner__remote_id', 'remote id'),
        ('reviewer', 'reviewer'),
        ('task_manager', 'task_manager'),
        ('promoter', 'promoter'),
        ('created', 'created'),
        ('status', 'status'),
        ('payout_status', 'payout status'),
        ('theme', 'theme'),
        ('location__group', 'region'),
        ('country', 'country'),
        ('location', 'location'),
        ('place', 'place'),
        ('deadline', 'deadline'),
        ('date_submitted', 'date submitted'),
        ('campaign_started', 'campaign started'),
        ('campaign_ended', 'campaign ended'),
        ('campaign_funded', 'campaign funded'),
        ('campaign_paid_out', 'campaign paid out'),
        ('task_count', 'task count'),
        ('supporters', 'supporters'),
        ('time_spent', 'time spent'),
        ('amount_asked', 'amount asked'),
        ('amount_donated', 'amount donated'),
        ('organization__name', 'organization'),
        ('amount_extra', 'amount matched'),
        ('expertise_based', 'expertise based'),
    ]

    actions = [export_as_csv_action(fields=export_fields), ]

    def get_actions(self, request):
        actions = super(ProjectAdmin, self).get_actions(request)
        for phase in ProjectPhase.objects.order_by('-sequence').all():
            action_name = 'mark_{}'.format(phase.slug)
            actions[action_name] = (
                mark_as, action_name, _('Mark selected as "{}"'.format(_(phase.name)))
            )
        return OrderedDict(reversed(actions.items()))

    # Fields
    def num_votes(self, obj):
        self.queryset(None)
        return obj.admin_vote_count

    num_votes.short_description = _('Vote Count')
    num_votes.admin_order_field = 'admin_vote_count'

    def get_title_display(self, obj):
        if len(obj.title) > 35:
            return format_html(
                u'<span title="{}" class="project-title">{} &hellip;</span>',
                obj.title, obj.title[:45]
            )
        return obj.title
    get_title_display.admin_order_field = 'title'
    get_title_display.short_description = _('title')

    def get_owner_display(self, obj):
        owner = obj.owner
        url = reverse('admin:members_member_change', args=[owner.id])
        return format_html(
            u"<a href='{}'>{}</a>",
            url,
            owner.get_full_name()
        )

    get_owner_display.admin_order_field = 'owner__last_name'
    get_owner_display.short_description = _('owner')

    def vote_count(self, obj):
        return obj.vote_set.count()

    def donated_percentage(self, obj):
        if obj.amount_donated.amount:
            percentage = 100 * obj.admin_donated_percentage
        else:
            percentage = 0
        return "{0:.2f} %".format(percentage)
    donated_percentage.short_description = _('Donated')
    donated_percentage.admin_order_field = 'admin_donated_percentage'

    def expertise_based(self, obj):
        return obj.expertise_based

    expertise_based.boolean = True

    @confirmation_form(PayoutApprovalConfirmationForm, 'admin/payout_approval_confirmation.html')
    def approve_payout(self, request, pk=None):
        project = Project.objects.get(pk=pk)
        project_url = reverse('admin:projects_project_change', args=(project.id,))

        # Check IBAN & BIC
        account = project.account_number
        if len(account) < 3:
            self.message_user(request, 'Invalid Bank Account: {}'.format(account), level='ERROR')
            return HttpResponseRedirect(project_url)

        if len(account) and account[0].isalpha():
            # Looks like an IBAN (starts with letter), let's check
            try:
                iban = IBAN(account)
            except ValueError as e:
                self.message_user(request, 'Invalid IBAN: {}'.format(e), level='ERROR')
                return HttpResponseRedirect(project_url)
            project.account_number = iban.compact
            try:
                bic = BIC(project.account_details)
            except ValueError as e:
                self.message_user(request, 'Invalid BIC: {}'.format(e), level='ERROR')
                return HttpResponseRedirect(project_url)
            project.account_details = bic.compact
            project.save()

        if not request.user.has_perm('projects.approve_payout'):
            self.message_user(request, 'Missing permission: projects.approve_payout', level='ERROR')
        elif project.payout_status != 'needs_approval':
            self.message_user(request, 'The payout does not have the status "needs approval"', level='ERROR')
        else:
            adapter = DoradoPayoutAdapter(project)
            try:
                adapter.trigger_payout()
            except PayoutValidationError as e:
                errors = e.message['errors']
                if type(errors) == unicode:
                    self.message_user(
                        request,
                        'Account details: {}.'.format(errors),
                        level=messages.ERROR
                    )
                else:
                    for field, errors in errors.items():
                        for error in errors:
                            self.message_user(
                                request,
                                'Account details: {}, {}.'.format(field, error.lower()),
                                level=messages.ERROR
                            )
            except (PayoutCreationError, ImproperlyConfigured) as e:
                logger.warning(
                    'Error approving payout: {}'.format(e),
                    exc_info=1
                )

                self.message_user(
                    request,
                    'Failed to approve payout: {}'.format(e),
                    level=messages.ERROR
                )

        return HttpResponseRedirect(project_url)

    @confirmation_form(RefundConfirmationForm, 'admin/refund_confirmation.html')
    def refund(self, request, pk=None, form=None):
        project = Project.objects.get(pk=pk)

        if not request.user.has_perm('payments.refund_orderpayment') or not project.can_refund:
            return HttpResponseForbidden('Missing permission: payments.refund_orderpayment')

        project.status = ProjectPhase.objects.get(slug='refunded')
        project.save()

        refund_project.delay(connection.tenant, project)

        project_url = reverse('admin:projects_project_change', args=(project.id,))
        return HttpResponseRedirect(project_url)

    reward_export_fields = (
        ('reward__title', 'Reward'),
        ('reward__description', 'Description'),
        ('order__id', 'Order id'),
        ('created', 'Donation Date'),
        ('reward__amount', 'Amount'),
        ('amount', 'Actual Amount'),
        ('order__user__email', 'Email'),
        ('order__user__full_name', 'Name'),
        ('name', 'Name on Donation')
    )

    def export_rewards(self, request, pk=None):
        """ Export all donations that include a reward.

        This allows the project initiator to contact all recipients.
        """
        project = Project.objects.get(pk=pk)
        if not request.user.is_staff:
            return HttpResponseForbidden('Missing permission: rewards.read_reward')

        response = HttpResponse(content_type='text/csv')
        response['Content-Disposition'] = 'attachment; filename="%s.csv"' % (
            unicode(slugify(project.title))
        )

        writer = csv.writer(response)

        writer.writerow([field[1] for field in self.reward_export_fields])

        for reward in project.donations.filter(reward__isnull=False):
            writer.writerow([
                prep_field(request, reward, field[0]) for field in self.reward_export_fields
            ])

        return response

    def amount_donated_i18n(self, obj):
        return obj.amount_donated

    amount_donated_i18n.short_description = _('Amount Donated')

    def amount_needed_i18n(self, obj):
        amount_needed = obj.amount_needed - obj.amount_extra
        if amount_needed.amount > 0:
            return amount_needed
        else:
            return Money(0, obj.amount_asked.currency)

    amount_needed_i18n.short_description = _('Amount Needed')

    # Setup
    def get_readonly_fields(self, request, obj=None):
        fields = [
            'created', 'updated',
            'vote_count', 'amount_donated_i18n', 'amount_needed_i18n',
            'popularity', 'payout_status',
            'geocoding', 'donations_link'
        ]
        if obj and obj.payout_status and obj.payout_status != 'needs_approval':
            fields += ('status', )
        return fields

    def get_urls(self):
        urls = super(ProjectAdmin, self).get_urls()
        process_urls = [
            url(r'^approve_payout/(?P<pk>\d+)/$',
                self.approve_payout,
                name="projects_project_approve_payout"),
            url(r'^refund/(?P<pk>\d+)/$',
                self.refund,
                name="projects_project_refund"),
            url(r'^export_rewards/(?P<pk>\d+)/$',
                self.export_rewards,
                name="projects_project_export_rewards"),
        ]
        return process_urls + urls

    def get_list_filter(self, request):
        filters = [
            ('status', UnionFieldListFilter),
            ('theme', TranslatedUnionFieldListFilter),
            ('task__skill', TranslatedUnionFieldListFilter),
            ProjectReviewerFilter,
            'categories',
            'project_type',
            'is_campaign'
        ]

        if request.user.has_perm('projects.approve_payout'):
            filters.insert(1, 'payout_status')

        # Only show Location column if there are any
        if Location.objects.count():
            filters += [LocationGroupFilter, LocationFilter]
        else:
            filters += [('country', admin.RelatedOnlyFieldListFilter), ]
        return filters

    def get_list_display(self, request):
        fields = [
            'get_title_display', 'get_owner_display', 'created_date',
            'status', 'deadline_date', 'donated_percentage',
            'expertise_based'
        ]

        if request.user.has_perm('projects.approve_payout'):
            fields.insert(4, 'payout_status')

        # Only show Location column if there are any
        if Location.objects.count():
            fields += ('location',)
        # Only show Vote_count column if there are any votes
        if Vote.objects.count():
            fields += ('vote_count',)
        return fields

    def lookup_allowed(self, key, value):
        if key == 'task__skill__expertise__exact':
            return True
        else:
            return super(ProjectAdmin, self).lookup_allowed(key, value)

    def created_date(self, obj):
        return obj.created.date()
    created_date.admin_order_field = 'created'
    created_date.short_description = _('Created')

    def deadline_date(self, obj):
        if obj.deadline:
            return obj.deadline.date()
        return None
    deadline_date.admin_order_field = 'deadline'
    deadline_date.short_description = _('Deadline')

    def get_fieldsets(self, request, obj=None):
        main = (_('Main'), {'fields': [
            'reviewer', 'title', 'slug', 'project_type',
            'status', 'owner', 'task_manager', 'promoter',
            'organization', 'is_campaign', 'celebrate_results'
        ]})

        story = (_('Story'), {'fields': [
            'pitch', 'story',
            'image', 'video_url',
            'theme', 'categories', 'language',
            'country', 'place',
        ]})

        if Location.objects.count():
            story[1]['fields'].append('location')

        amount = (_('Amount'), {'fields': [
            'amount_asked', 'amount_extra', 'amount_donated_i18n', 'amount_needed_i18n',
            'currencies', 'donations_link', 'popularity', 'vote_count'
        ]})

        if request.user.has_perm('projects.approve_payout'):
            amount[1]['fields'].insert(0, 'payout_status')

        dates = (_('Dates'), {'fields': [
            'created', 'updated',
            'deadline', 'date_submitted', 'campaign_started',
            'campaign_ended', 'campaign_funded',
            'campaign_paid_out', 'voting_deadline'
        ]})

        bank = (_('Bank details'), {'fields': [
            'account_holder_name',
            'account_holder_address',
            'account_holder_postal_code',
            'account_holder_city',
            'account_holder_country',
            'account_number',
            'account_details',
            'account_bank_country',
            'bank_details_reviewed'
        ]})

        extra = (_('Extra fields'), {
            'fields': [field.slug for field in CustomProjectFieldSettings.objects.all()]
        })

        fieldsets = (main, story, dates)

        if obj:
            project = Project.objects.get(pk=obj.id)
            if project.project_type != 'sourcing':
                fieldsets += (amount, bank)

        if CustomProjectFieldSettings.objects.count():
            fieldsets += (extra, )

        return fieldsets

    def get_queryset(self, request):
        # Optimization: Select related fields that are used in admin specific
        # display fields.
        queryset = super(ProjectAdmin, self).get_queryset(request)
        queryset = queryset.select_related(
            'owner', 'organization'
        ).annotate(
            admin_vote_count=Count('vote', distinct=True),
            admin_donated_percentage=Case(
                When(amount_asked__gt=0, then=F('amount_donated') / F('amount_asked')),
                default=0
            ),
            time_spent=Sum('task__members__time_spent')
        )

        return queryset

    def donations_link(self, obj):
        url = reverse('admin:donations_donation_changelist')
        total = obj.donations.count()
        return format_html('<a href="{}?project_id={}">{} {}</a>'.format(url, obj.id, total, _('donations')))
    donations_link.short_description = _("Donations")