Source code for adhocracy_core.sheets.badge

"""Badge sheet."""
from colander import deferred
from colander import All
from colander import Invalid
from pyramid.request import Request
from pyramid.traversal import lineage
from pyramid.traversal import resource_path
from substanced.util import find_service

from adhocracy_core.interfaces import ISheet
from adhocracy_core.interfaces import ISheetReferenceAutoUpdateMarker
from adhocracy_core.interfaces import SheetToSheet
from adhocracy_core.interfaces import search_query
from adhocracy_core.schema import SchemaNode
from adhocracy_core.schema import MappingSchema
from adhocracy_core.schema import PostPool
from adhocracy_core.schema import Reference
from adhocracy_core.schema import UniqueReferences
from adhocracy_core.schema import create_post_pool_validator
from adhocracy_core.schema import validate_reftype
from adhocracy_core.sheets import add_sheet_to_registry
from adhocracy_core.sheets import sheet_meta
from adhocracy_core.sheets.name import IName
from adhocracy_core.sheets.pool import IPool


[docs]class IBadge(ISheet): """Marker interface for badge data sheet."""
[docs]class IHasBadgesPool(ISheet): """Marker interface for resources that have a badge datas pool."""
[docs]class ICanBadge(ISheet): """Marker interface for principals that can assign badges."""
[docs]class IBadgeable(ISheet): """Marker interface for resources that can be badged."""
[docs]class IBadgeAssignment(ISheet, ISheetReferenceAutoUpdateMarker): """Marker interface for the badge assignment sheet."""
[docs]class BadgeReference(SheetToSheet): """Reference from badge to badge data resource.""" source_isheet = IBadgeAssignment source_isheet_field = 'badge' target_isheet = IBadge
[docs]class BadgeSubjectReference(SheetToSheet): """Reference from badge to assigning user.""" source_isheet = IBadgeAssignment source_isheet_field = 'subject' target_isheet = ICanBadge
[docs]class BadgeObjectReference(SheetToSheet): """Reference from badge to badged content.""" source_isheet = IBadgeAssignment source_isheet_field = 'object' target_isheet = IBadgeable
[docs]class BadgeGroupReference(SheetToSheet): """Reference from badge to badge group.""" source_isheet = IBadge source_isheet_field = 'groups' target_isheet = IPool # TODO add special sheet for badge groups
@deferred def deferred_groups_default(node: SchemaNode, kw: dict) -> []: """Return badge groups.""" from adhocracy_core.resources.badge import IBadgeGroup # no circle imports context = kw['context'] parents = [x for x in lineage(context)][1:] groups = [] for parent in parents: if IBadgeGroup.providedBy(parent): groups.append(parent) else: break return groups
[docs]class BadgeSchema(MappingSchema): """Badge sheet data structure.""" groups = UniqueReferences(reftype=BadgeGroupReference, readonly=True, default=deferred_groups_default, )
badge_meta = sheet_meta._replace(isheet=IBadge, schema_class=BadgeSchema, )
[docs]class HasBadgesPoolSchema(MappingSchema): """Data structure pointing to a badges post pool.""" badges_pool = PostPool(iresource_or_service_name='badges')
has_badges_pool_meta = sheet_meta._replace( isheet=IHasBadgesPool, schema_class=HasBadgesPoolSchema, editable=False, creatable=False, )
[docs]class CanBadgeSchema(MappingSchema): """CanBadge sheet data structure."""
can_badge_meta = sheet_meta._replace( isheet=ICanBadge, schema_class=CanBadgeSchema, editable=False, creatable=False, )
[docs]class BadgeableSchema(MappingSchema): """Badgeable sheet data structure. `post_pool`: Pool to post new :class:`adhocracy_sample.resource.IBadgeAssignment`. """ assignments = UniqueReferences(readonly=True, backref=True, reftype=BadgeObjectReference) post_pool = PostPool(iresource_or_service_name='badge_assignments')
badgeable_meta = sheet_meta._replace( isheet=IBadgeable, schema_class=BadgeableSchema, editable=False, creatable=False, )
[docs]def create_unique_badge_assignment_validator(badge_ref: Reference, object_ref: Reference, kw: dict) -> callable: """Create validator to check that a badge assignment is unique. Badge assignments is considered unique if there is at most one for each badge in :term:`post_pool`. :param badge: Reference to a sheet with :term:`post_pool` field. :param kw: dictionary with keys `context` and `registry`. """ context = kw['context'] registry = kw['registry'] def validator(node, value): new_badge = node.get_value(value, badge_ref.name) new_badge_name = registry.content.get_sheet_field(new_badge, IName, 'name') new_object = node.get_value(value, object_ref.name) pool = find_service(context, 'badge_assignments') for badge_assignment in pool.values(): badge_sheet_values = registry.content.get_sheet( badge_assignment, IBadgeAssignment).get() badge = badge_sheet_values['badge'] badge_name = registry.content.get_sheet_field(badge, IName, 'name') obj = badge_sheet_values['object'] updating_current_assignment = context == badge_assignment if new_badge_name == badge_name \ and new_object == obj \ and not updating_current_assignment: raise Invalid(badge_ref, 'Badge already assigned') return validator
@deferred def deferred_validate_badge(node, kw): """Check `assign_badge` permission and ISheet interface of `badge` node.""" request = kw['request'] def check_assign_badge_permisson(node, value): if not request.has_permission('assign_badge', value): badge_path = resource_path(value) raise Invalid(node, 'Your are missing the `assign_badge` ' ' permission for: ' + badge_path) return All(validate_reftype, check_assign_badge_permisson, )
[docs]class BadgeAssignmentSchema(MappingSchema): """Badge sheet data structure.""" subject = Reference(reftype=BadgeSubjectReference) badge = Reference(reftype=BadgeReference, validator=deferred_validate_badge ) object = Reference(reftype=BadgeObjectReference) @deferred
[docs] def validator(self, kw: dict) -> callable: """Validate the :term:`post_pool` for the object reference.""" object_validator = create_post_pool_validator(self['object'], kw) badge_assignment_validator = create_unique_badge_assignment_validator( self['badge'], self['object'], kw) return All(object_validator, badge_assignment_validator)
badge_assignment_meta = sheet_meta._replace( isheet=IBadgeAssignment, schema_class=BadgeAssignmentSchema, )
[docs]def get_assignable_badges(context: IBadgeable, request: Request) -> [IBadge]: """Get assignable badges for the IBadgeAssignment sheet.""" badges = find_service(context, 'badges') if badges is None: return [] catalogs = find_service(context, 'catalogs') query = search_query._replace(root=badges, interfaces=IBadge, allows=(request.effective_principals, 'assign_badge'), ) result = catalogs.search(query) return result.elements
[docs]def includeme(config): """Register sheets, adapters and index views.""" add_sheet_to_registry(badge_assignment_meta, config.registry) add_sheet_to_registry(badge_meta, config.registry) add_sheet_to_registry(badgeable_meta, config.registry) add_sheet_to_registry(can_badge_meta, config.registry) add_sheet_to_registry(has_badges_pool_meta, config.registry)