class AccessTokenSerializerV2(DetailSerializerV2): application = ApplicationInfoSerializer(source='applicationinfo') scope = serializers.CharField(read_only=True) expires = DateTimeWithUtcZAtEndField(read_only=True) created = DateTimeWithUtcZAtEndField(read_only=True) class Meta: list_serializer_class = ListSerializerV2 apispec_definition = ('AccessToken', {})
class BackpackAssertionSerializerV2(DetailSerializerV2, OriginalJsonSerializerMixin): acceptance = serializers.ChoiceField( choices=BadgeInstance.ACCEPTANCE_CHOICES, default=BadgeInstance.ACCEPTANCE_ACCEPTED) # badgeinstance openBadgeId = serializers.URLField(source='jsonld_id', read_only=True) badgeclass = EntityRelatedFieldV2(source='cached_badgeclass', required=False, queryset=BadgeClass.cached) badgeclassOpenBadgeId = serializers.URLField(source='badgeclass_jsonld_id', read_only=True) issuer = EntityRelatedFieldV2(source='cached_issuer', required=False, queryset=Issuer.cached) issuerOpenBadgeId = serializers.URLField(source='issuer_jsonld_id', read_only=True) image = serializers.FileField(read_only=True) recipient = BadgeRecipientSerializerV2(source='*') issuedOn = DateTimeWithUtcZAtEndField(source='issued_on', read_only=True) narrative = MarkdownCharField(required=False) evidence = EvidenceItemSerializerV2(many=True, required=False) revoked = HumanReadableBooleanField(read_only=True) revocationReason = serializers.CharField(source='revocation_reason', read_only=True) expires = DateTimeWithUtcZAtEndField(source='expires_at', required=False) pending = serializers.ReadOnlyField() class Meta(DetailSerializerV2.Meta): model = BadgeInstance def to_representation(self, instance): representation = super(BackpackAssertionSerializerV2, self).to_representation(instance) request_kwargs = self.context['kwargs'] expands = request_kwargs.get('expands', []) if self.parent is not None: # we'll have a bare representation instance_data_pointer = representation else: instance_data_pointer = representation['result'][0] if 'badgeclass' in expands: instance_data_pointer[ 'badgeclass'] = instance.cached_badgeclass.get_json( include_extra=True, use_canonical_id=True) if 'issuer' in expands: instance_data_pointer['badgeclass'][ 'issuer'] = instance.cached_issuer.get_json( include_extra=True, use_canonical_id=True) return representation
class BadgrSocialAccountSerializerV1(serializers.Serializer): id = serializers.CharField() provider = serializers.CharField() dateAdded = DateTimeWithUtcZAtEndField(source='date_joined') uid = serializers.CharField() def to_representation(self, instance): representation = super(BadgrSocialAccountSerializerV1, self).to_representation(instance) provider = instance.get_provider() common_fields = provider.extract_common_fields(instance.extra_data) email = common_fields.get('email', None) url = common_fields.get('url', None) if not email and 'userPrincipalName' in instance.extra_data: email = instance.extra_data['userPrincipalName'] representation.update({ 'firstName': common_fields.get('first_name', common_fields.get('name', None)), 'lastName': common_fields.get('last_name', None), 'primaryEmail': email, 'url': url, }) return representation
class BadgrSocialAccountSerializerV2(BaseSerializerV2): id = serializers.CharField() provider = serializers.CharField() dateAdded = DateTimeWithUtcZAtEndField(source='date_joined') uid = serializers.CharField() class Meta: list_serializer_class = ListSerializerV2 def to_representation(self, instance): representation = super(BadgrSocialAccountSerializerV2, self).to_representation(instance) provider = instance.get_provider() common_fields = provider.extract_common_fields(instance.extra_data) email = common_fields.get('email', None) url = common_fields.get('url', None) if not email and 'userPrincipalName' in instance.extra_data: email = instance.extra_data['userPrincipalName'] if self.parent is None: result = representation['result'][0] else: result = representation result.update({ 'firstName': common_fields.get('first_name', None), 'lastName': common_fields.get('last_name', None), 'primaryEmail': email, 'url': url, }) return representation
class IssuerAccessTokenSerializerV2(BaseSerializerV2): token = serializers.CharField() issuer = serializers.CharField() expires = DateTimeWithUtcZAtEndField() class Meta(DetailSerializerV2.Meta): apispec_definition = ('AccessToken', {}) def to_representation(self, instance): return super(IssuerAccessTokenSerializerV2, self).to_representation(instance)
class RecipientGroupSerializerV2(DetailSerializerV2): createdAt = DateTimeWithUtcZAtEndField(source='created_at', read_only=True) createdBy = EntityRelatedFieldV2(source='cached_creator', read_only=True) issuer = EntityRelatedFieldV2(source='cached_issuer', read_only=True) name = StripTagsCharField(required=False) description = StripTagsCharField(required=False) active = serializers.BooleanField(source='is_active', default=True) members = RecipientGroupMembershipSerializerV2(many=True, source='membership_items', required=False) pathways = EntityRelatedFieldV2(many=True, source='pathway_items', required=False, queryset=Pathway.cached) class Meta(DetailSerializerV2.Meta): model = RecipientGroup def update(self, instance, validated_data): if 'cached_issuer' in validated_data: validated_data.pop('cached_issuer') # issuer is not updatedable super(RecipientGroupSerializerV2, self).update(instance, validated_data) def create(self, validated_data): if 'cached_issuer' in validated_data: # included issuer in request validated_data['issuer'] = validated_data.pop('cached_issuer') elif 'issuer' in self.context: # issuer was passed in context validated_data['issuer'] = self.context.get('issuer') else: # issuer is required on create raise serializers.ValidationError( {"issuer": "This field is required"}) return super(RecipientGroupSerializerV2, self).create(validated_data)
class BadgrSocialAccountSerializerV1(serializers.Serializer): id = serializers.CharField() provider = serializers.CharField() dateAdded = DateTimeWithUtcZAtEndField(source='date_joined') uid = serializers.CharField() def to_representation(self, instance): representation = super(BadgrSocialAccountSerializerV1, self).to_representation(instance) try: provider = instance.get_provider() common_fields = provider.extract_common_fields(instance.extra_data) except AttributeError: # For SAML handling common_fields = dict() representation['id'] = instance.account_identifier email = common_fields.get('email', None) url = common_fields.get('url', None) if not email and hasattr( instance, 'extra_data') and 'userPrincipalName' in instance.extra_data: email = instance.extra_data['userPrincipalName'] representation.update({ 'firstName': common_fields.get('first_name', common_fields.get('name', None)), 'lastName': common_fields.get('last_name', None), 'primaryEmail': email, 'url': url, }) return representation
class BadgeClassSerializerV1(OriginalJsonSerializerMixin, serializers.Serializer): created_at = DateTimeWithUtcZAtEndField(read_only=True) created_by = BadgeUserIdentifierFieldV1() id = serializers.IntegerField(required=False, read_only=True) name = StripTagsCharField(max_length=255) image = ValidImageField(required=False) slug = StripTagsCharField(max_length=255, read_only=True, source='entity_id') criteria = MarkdownCharField(allow_blank=True, required=False, write_only=True) criteria_text = MarkdownCharField(required=False, allow_null=True, allow_blank=True) criteria_url = StripTagsCharField(required=False, allow_blank=True, allow_null=True, validators=[URLValidator()]) recipient_count = serializers.IntegerField(required=False, read_only=True) pathway_element_count = serializers.IntegerField(required=False, read_only=True) description = StripTagsCharField(max_length=16384, required=True, convert_null=True) alignment = AlignmentItemSerializerV1(many=True, source='alignment_items', required=False) tags = serializers.ListField(child=StripTagsCharField(max_length=1024), source='tag_items', required=False) expires = BadgeClassExpirationSerializerV1(source='*', required=False, allow_null=True) class Meta: apispec_definition = ('BadgeClass', {}) def to_internal_value(self, data): if 'expires' in data: if not data['expires'] or len(data['expires']) == 0: # if expires was included blank, remove it so to_internal_value() doesnt choke del data['expires'] return super(BadgeClassSerializerV1, self).to_internal_value(data) def to_representation(self, instance): representation = super(BadgeClassSerializerV1, self).to_representation(instance) representation['issuer'] = OriginSetting.HTTP + reverse( 'issuer_json', kwargs={'entity_id': instance.cached_issuer.entity_id}) representation['json'] = instance.get_json(obi_version='1_1', use_canonical_id=True) return representation def validate_image(self, image): if image is not None: img_name, img_ext = os.path.splitext(image.name) image.name = 'issuer_badgeclass_' + str(uuid.uuid4()) + img_ext return image def validate_criteria_text(self, criteria_text): if criteria_text is not None and criteria_text != '': return criteria_text else: return None def validate_criteria_url(self, criteria_url): if criteria_url is not None and criteria_url != '': return criteria_url else: return None def update(self, instance, validated_data): force_image_resize = False new_name = validated_data.get('name') if new_name: new_name = strip_tags(new_name) instance.name = new_name new_description = validated_data.get('description') if new_description: instance.description = strip_tags(new_description) if 'criteria_text' in validated_data: instance.criteria_text = validated_data.get('criteria_text') if 'criteria_url' in validated_data: instance.criteria_url = validated_data.get('criteria_url') if 'image' in validated_data: instance.image = validated_data.get('image') force_image_resize = True instance.alignment_items = validated_data.get('alignment_items') instance.tag_items = validated_data.get('tag_items') instance.expires_amount = validated_data.get('expires_amount', None) instance.expires_duration = validated_data.get('expires_duration', None) instance.save(force_resize=force_image_resize) return instance def validate(self, data): if 'criteria' in data: if 'criteria_url' in data or 'criteria_text' in data: raise serializers.ValidationError( "The criteria field is mutually-exclusive with the criteria_url and criteria_text fields" ) if utils.is_probable_url(data.get('criteria')): data['criteria_url'] = data.pop('criteria') elif not isinstance(data.get('criteria'), str): raise serializers.ValidationError( "Provided criteria text could not be properly processed as URL or plain text." ) else: data['criteria_text'] = data.pop('criteria') else: if data.get('criteria_text', None) is None and data.get( 'criteria_url', None) is None: raise serializers.ValidationError( "One or both of the criteria_text and criteria_url fields must be provided" ) return data def create(self, validated_data, **kwargs): if 'image' not in validated_data: raise serializers.ValidationError( {"image": ["This field is required"]}) if 'issuer' in self.context: validated_data['issuer'] = self.context.get('issuer') new_badgeclass = BadgeClass.objects.create(**validated_data) return new_badgeclass
class BadgeClassSerializerV1(OriginalJsonSerializerMixin, ExtensionsSaverMixin, serializers.Serializer): created_at = DateTimeWithUtcZAtEndField(read_only=True) created_by = BadgeUserIdentifierFieldV1() id = serializers.IntegerField(required=False, read_only=True) name = StripTagsCharField(max_length=255) image = ValidImageField(required=False) slug = StripTagsCharField(max_length=255, read_only=True, source='entity_id') criteria = MarkdownCharField(allow_blank=True, required=False, write_only=True) criteria_text = MarkdownCharField(required=False, allow_null=True, allow_blank=True) criteria_url = StripTagsCharField(required=False, allow_blank=True, allow_null=True, validators=[URLValidator()]) recipient_count = serializers.IntegerField(required=False, read_only=True, source='v1_api_recipient_count') description = StripTagsCharField(max_length=16384, required=True, convert_null=True) alignment = AlignmentItemSerializerV1(many=True, source='alignment_items', required=False) tags = serializers.ListField(child=StripTagsCharField(max_length=1024), source='tag_items', required=False) extensions = serializers.DictField(source='extension_items', required=False, validators=[BadgeExtensionValidator()]) expires = BadgeClassExpirationSerializerV1(source='*', required=False, allow_null=True) # issuerName = StripTagsCharField(max_length=255) class Meta: apispec_definition = ('BadgeClass', {}) def to_internal_value(self, data): if 'expires' in data: if not data['expires'] or len(data['expires']) == 0: # if expires was included blank, remove it so to_internal_value() doesnt choke del data['expires'] return super(BadgeClassSerializerV1, self).to_internal_value(data) def to_representation(self, instance): representation = super(BadgeClassSerializerV1, self).to_representation(instance) representation['issuerName'] = instance.cached_issuer.name representation['issuer'] = OriginSetting.HTTP + reverse( 'issuer_json', kwargs={'entity_id': instance.cached_issuer.entity_id}) representation['json'] = instance.get_json(obi_version='1_1', use_canonical_id=True) return representation def validate_image(self, image): if image is not None: img_name, img_ext = os.path.splitext(image.name) image.name = 'issuer_badgeclass_' + str(uuid.uuid4()) + img_ext return image def validate_criteria_text(self, criteria_text): if criteria_text is not None and criteria_text != '': return criteria_text else: return None def validate_criteria_url(self, criteria_url): if criteria_url is not None and criteria_url != '': return criteria_url else: return None def validate_extensions(self, extensions): is_formal = False if extensions: for ext_name, ext in extensions.items(): # if "@context" in ext and not ext['@context'].startswith(settings.EXTENSIONS_ROOT_URL): # raise BadgrValidationError( # error_code=999, # error_message=f"extensions @context invalid {ext['@context']}") if ext_name.endswith('ECTSExtension') or ext_name.endswith( 'StudyLoadExtension') or ext_name.endswith( 'CategoryExtension') or ext_name.endswith( 'LevelExtension'): is_formal = True self.formal = is_formal return extensions def add_extensions(self, instance, add_these_extensions, extension_items): for extension_name in add_these_extensions: original_json = extension_items[extension_name] extension = BadgeClassExtension( name=extension_name, original_json=json.dumps(original_json), badgeclass_id=instance.pk) extension.save() def update(self, instance, validated_data): logger.info("UPDATE BADGECLASS") logger.debug(validated_data) force_image_resize = False new_name = validated_data.get('name') if new_name: new_name = strip_tags(new_name) instance.name = new_name new_description = validated_data.get('description') if new_description: instance.description = strip_tags(new_description) # Assure both criteria_url and criteria_text will not be empty if 'criteria_url' in validated_data or 'criteria_text' in validated_data: end_criteria_url = validated_data['criteria_url'] if 'criteria_url' in validated_data \ else instance.criteria_url end_criteria_text = validated_data['criteria_text'] if 'criteria_text' in validated_data \ else instance.criteria_text if ((end_criteria_url is None or not end_criteria_url.strip()) and (end_criteria_text is None or not end_criteria_text.strip())): raise serializers.ValidationError( 'Changes cannot be made that would leave both criteria_url and criteria_text blank.' ) else: instance.criteria_text = end_criteria_text instance.criteria_url = end_criteria_url if 'image' in validated_data: instance.image = validated_data.get('image') force_image_resize = True instance.alignment_items = validated_data.get('alignment_items') instance.tag_items = validated_data.get('tag_items') instance.expires_amount = validated_data.get('expires_amount', None) instance.expires_duration = validated_data.get('expires_duration', None) logger.debug("SAVING EXTENSION") self.save_extensions(validated_data, instance) instance.save(force_resize=force_image_resize) return instance def validate(self, data): if 'criteria' in data: if 'criteria_url' in data or 'criteria_text' in data: raise serializers.ValidationError( "The criteria field is mutually-exclusive with the criteria_url and criteria_text fields" ) if utils.is_probable_url(data.get('criteria')): data['criteria_url'] = data.pop('criteria') elif not isinstance(data.get('criteria'), str): raise serializers.ValidationError( "Provided criteria text could not be properly processed as URL or plain text." ) else: data['criteria_text'] = data.pop('criteria') return data def create(self, validated_data, **kwargs): logger.info("CREATE NEW BADGECLASS") logger.debug(validated_data) if 'image' not in validated_data: raise serializers.ValidationError( {"image": ["This field is required"]}) if 'issuer' in self.context: validated_data['issuer'] = self.context.get('issuer') if validated_data.get('criteria_text', None) is None and validated_data.get( 'criteria_url', None) is None: raise serializers.ValidationError( "One or both of the criteria_text and criteria_url fields must be provided" ) new_badgeclass = BadgeClass.objects.create(**validated_data) return new_badgeclass
class IssuerSerializerV2(DetailSerializerV2, OriginalJsonSerializerMixin): openBadgeId = serializers.URLField(source='jsonld_id', read_only=True) createdAt = DateTimeWithUtcZAtEndField(source='created_at', read_only=True) createdBy = EntityRelatedFieldV2(source='cached_creator', queryset=BadgeUser.cached, required=False) name = StripTagsCharField(max_length=1024) image = ValidImageField(required=False) email = serializers.EmailField(max_length=255, required=True) description = StripTagsCharField(max_length=16384, required=False) url = serializers.URLField(max_length=1024, required=True) staff = IssuerStaffSerializerV2(many=True, source='staff_items', required=False) extensions = serializers.DictField(source='extension_items', required=False, validators=[BadgeExtensionValidator()]) badgrDomain = serializers.SlugRelatedField(required=False, source='badgrapp', slug_field='cors', queryset=BadgrApp.objects) class Meta(DetailSerializerV2.Meta): model = Issuer apispec_definition = ('Issuer', { 'properties': OrderedDict([ ('entityId', { 'type': "string", 'format': "string", 'description': "Unique identifier for this Issuer", 'readOnly': True, }), ('entityType', { 'type': "string", 'format': "string", 'description': "\"Issuer\"", 'readOnly': True, }), ('openBadgeId', { 'type': "string", 'format': "url", 'description': "URL of the OpenBadge compliant json", 'readOnly': True, }), ('createdAt', { 'type': 'string', 'format': 'ISO8601 timestamp', 'description': "Timestamp when the Issuer was created", 'readOnly': True, }), ('createdBy', { 'type': 'string', 'format': 'entityId', 'description': "BadgeUser who created this Issuer", 'required': False, }), ('name', { 'type': "string", 'format': "string", 'description': "Name of the Issuer", 'required': True, }), ('image', { 'type': "string", 'format': "data:image/png;base64", 'description': "Base64 encoded string of an image that represents the Issuer", 'required': False, }), ('email', { 'type': "string", 'format': "email", 'description': "Contact email for the Issuer", 'required': True, }), ('url', { 'type': "string", 'format': "url", 'description': "Homepage or website associated with the Issuer", 'required': False, }), ('description', { 'type': "string", 'format': "text", 'description': "Short description of the Issuer", 'required': False, }), ]) }) def validate_image(self, image): if image is not None: img_name, img_ext = os.path.splitext(image.name) image.name = 'issuer_logo_' + str(uuid.uuid4()) + img_ext return image def validate_createdBy(self, val): if not request_authenticated_with_server_admin_token( self.context.get('request')): return None return val def validate(self, data): if data.get('badgrapp' ) and not request_authenticated_with_server_admin_token( self.context.get('request')): data.pop('badgrapp') return data def create(self, validated_data): request = self.context.get('request') # If a Server Admin declares another user as creator, set it to that other user. Otherwise, use request.user user = validated_data.pop('cached_creator', None) if user: validated_data['created_by'] = user potential_email = validated_data['email'] if validated_data.get('badgrapp') is None: validated_data['badgrapp'] = BadgrApp.objects.get_current(request) # Server admins are exempt from email verification requirement. They will enforce it themselves. if not request_authenticated_with_server_admin_token( request) and not validated_data[ 'created_by'].is_email_verified(potential_email): raise serializers.ValidationError( "Issuer email must be one of your verified addresses. Add this email to your profile and try again." ) staff = validated_data.pop('staff_items', []) new_issuer = super(IssuerSerializerV2, self).create(validated_data) # update staff after issuer is created new_issuer.staff_items = staff return new_issuer def update(self, instance, validated_data): validated_data.pop('cached_creator', None) if 'image' in validated_data: self.context['save_kwargs'] = dict(force_resize=True) return super(IssuerSerializerV2, self).update(instance, validated_data)
class BadgeInstanceSerializerV2(DetailSerializerV2, OriginalJsonSerializerMixin): openBadgeId = serializers.URLField(source='jsonld_id', read_only=True) createdAt = DateTimeWithUtcZAtEndField(source='created_at', read_only=True, default_timezone=pytz.utc) createdBy = EntityRelatedFieldV2(source='cached_creator', read_only=True) badgeclass = EntityRelatedFieldV2(source='cached_badgeclass', required=False, queryset=BadgeClass.cached) badgeclassOpenBadgeId = CachedUrlHyperlinkedRelatedField( source='badgeclass_jsonld_id', view_name='badgeclass_json', lookup_field='entity_id', queryset=BadgeClass.cached, required=False) badgeclassName = serializers.CharField(write_only=True, required=False) issuer = EntityRelatedFieldV2(source='cached_issuer', required=False, queryset=Issuer.cached) issuerOpenBadgeId = serializers.URLField(source='issuer_jsonld_id', read_only=True) image = serializers.FileField(read_only=True) recipient = BadgeRecipientSerializerV2(source='*', required=False) issuedOn = DateTimeWithUtcZAtEndField(source='issued_on', required=False, default_timezone=pytz.utc) narrative = MarkdownCharField(required=False, allow_null=True) evidence = EvidenceItemSerializerV2(source='evidence_items', many=True, required=False) revoked = HumanReadableBooleanField(read_only=True) revocationReason = serializers.CharField(source='revocation_reason', read_only=True) acceptance = serializers.CharField(read_only=True) expires = DateTimeWithUtcZAtEndField(source='expires_at', required=False, allow_null=True, default_timezone=pytz.utc) notify = HumanReadableBooleanField(write_only=True, required=False, default=False) allowDuplicateAwards = serializers.BooleanField(write_only=True, required=False, default=True) extensions = serializers.DictField(source='extension_items', required=False, validators=[BadgeExtensionValidator()]) class Meta(DetailSerializerV2.Meta): model = BadgeInstance apispec_definition = ('Assertion', { 'properties': OrderedDict([ ('entityId', { 'type': "string", 'format': "string", 'description': "Unique identifier for this Assertion", 'readOnly': True, }), ('entityType', { 'type': "string", 'format': "string", 'description': "\"Assertion\"", 'readOnly': True, }), ('openBadgeId', { 'type': "string", 'format': "url", 'description': "URL of the OpenBadge compliant json", 'readOnly': True, }), ('createdAt', { 'type': 'string', 'format': 'ISO8601 timestamp', 'description': "Timestamp when the Assertion was created", 'readOnly': True, }), ('createdBy', { 'type': 'string', 'format': 'entityId', 'description': "BadgeUser who created the Assertion", 'readOnly': True, }), ('badgeclass', { 'type': 'string', 'format': 'entityId', 'description': "BadgeClass that issued this Assertion", 'required': False, }), ('badgeclassOpenBadgeId', { 'type': 'string', 'format': 'url', 'description': "URL of the BadgeClass to award", 'required': False, }), ('badgeclassName', { 'type': 'string', 'format': 'string', 'description': "Name of BadgeClass to create assertion against, case insensitive", 'required': False, }), ('revoked', { 'type': 'boolean', 'description': "True if this Assertion has been revoked", 'readOnly': True, }), ('revocationReason', { 'type': 'string', 'format': "string", 'description': "Short description of why the Assertion was revoked", 'readOnly': True, }), ('acceptance', { 'type': 'string', 'description': "Recipient interaction with Assertion. One of: Unaccepted, Accepted, or Rejected", 'readOnly': True, }), ('image', { 'type': 'string', 'format': 'url', 'description': "URL to the baked assertion image", 'readOnly': True, }), ('issuedOn', { 'type': 'string', 'format': 'ISO8601 timestamp', 'description': "Timestamp when the Assertion was issued", 'required': False, }), ('narrative', { 'type': 'string', 'format': 'markdown', 'description': "Markdown narrative of the achievement", 'required': False, }), ('evidence', { 'type': 'array', 'items': { '$ref': '#/definitions/AssertionEvidence' }, 'description': "List of evidence associated with the achievement", 'required': False, }), ('recipient', { 'type': 'object', '$ref': '#/definitions/BadgeRecipient', 'description': "Recipient that was issued the Assertion", 'required': False, }), ('expires', { 'type': 'string', 'format': 'ISO8601 timestamp', 'description': "Timestamp when the Assertion expires", 'required': False, }), ]) }) def validate_issuedOn(self, value): if value > timezone.now(): raise serializers.ValidationError( "Only issuedOn dates in the past are acceptable.") if value.year < 1583: raise serializers.ValidationError( "Only issuedOn dates after the introduction of the Gregorian calendar are allowed." ) return value def update(self, instance, validated_data): updateable_fields = [ 'evidence_items', 'expires_at', 'extension_items', 'hashed', 'issued_on', 'narrative', 'recipient_identifier', 'recipient_type' ] for field_name in updateable_fields: if field_name in validated_data: setattr(instance, field_name, validated_data.get(field_name)) instance.rebake(save=True) return instance def validate(self, data): request = self.context.get('request', None) expected_issuer = self.context.get('kwargs', {}).get('issuer') badgeclass_identifiers = [ 'badgeclass_jsonld_id', 'badgeclassName', 'cached_badgeclass', 'badgeclass' ] badge_instance_properties = list(data.keys()) if 'badgeclass' in self.context: badge_instance_properties.append('badgeclass') if sum( [el in badgeclass_identifiers for el in badge_instance_properties]) > 1: raise serializers.ValidationError( 'Multiple badge class identifiers. Exactly one of the following badge class identifiers are allowed: badgeclass, badgeclassName, or badgeclassOpenBadgeId' ) if request and request.method != 'PUT': # recipient and badgeclass are only required on create, ignored on update if 'recipient_identifier' not in data: raise serializers.ValidationError( {'recipient': ["This field is required"]}) if 'cached_badgeclass' in data: # included badgeclass in request data['badgeclass'] = data.pop('cached_badgeclass') elif 'badgeclass' in self.context: # badgeclass was passed in context data['badgeclass'] = self.context.get('badgeclass') elif 'badgeclass_jsonld_id' in data: data['badgeclass'] = data.pop('badgeclass_jsonld_id') elif 'badgeclassName' in data: name = data.pop('badgeclassName') matches = BadgeClass.objects.filter(name=name, issuer=expected_issuer) len_matches = len(matches) if len_matches == 1: data['badgeclass'] = matches.first() elif len_matches == 0: raise serializers.ValidationError( "No matching BadgeClass found with name {}".format( name)) else: raise serializers.ValidationError( "Could not award; {} BadgeClasses with name {}".format( len_matches, name)) else: raise serializers.ValidationError( {"badgeclass": ["This field is required"]}) allow_duplicate_awards = data.pop('allowDuplicateAwards') if allow_duplicate_awards is False: previous_awards = BadgeInstance.objects.filter( recipient_identifier=data['recipient_identifier'], badgeclass=data['badgeclass']).filter( revoked=False).filter( Q(expires_at__isnull=True) | Q(expires_at__gt=timezone.now())) if previous_awards.exists(): raise serializers.ValidationError( "A previous award of this badge already exists for this recipient." ) if expected_issuer and data[ 'badgeclass'].issuer_id != expected_issuer.id: raise serializers.ValidationError({ "badgeclass": ["Could not find matching badgeclass for this issuer."] }) if 'badgeclass' in data: data['issuer'] = data['badgeclass'].issuer return data
class BadgeClassSerializerV2(DetailSerializerV2, OriginalJsonSerializerMixin): openBadgeId = serializers.URLField(source='jsonld_id', read_only=True) createdAt = DateTimeWithUtcZAtEndField(source='created_at', read_only=True) createdBy = EntityRelatedFieldV2(source='cached_creator', read_only=True) issuer = EntityRelatedFieldV2(source='cached_issuer', required=False, queryset=Issuer.cached) issuerOpenBadgeId = serializers.URLField(source='issuer_jsonld_id', read_only=True) name = StripTagsCharField(max_length=1024) image = ValidImageField(required=False) description = StripTagsCharField(max_length=16384, required=True, convert_null=True) criteriaUrl = StripTagsCharField(source='criteria_url', required=False, allow_null=True, validators=[URLValidator()]) criteriaNarrative = MarkdownCharField(source='criteria_text', required=False, allow_null=True) alignments = AlignmentItemSerializerV2(source='alignment_items', many=True, required=False) tags = serializers.ListField(child=StripTagsCharField(max_length=1024), source='tag_items', required=False) expires = BadgeClassExpirationSerializerV2(source='*', required=False, allow_null=True) extensions = serializers.DictField(source='extension_items', required=False, validators=[BadgeExtensionValidator()]) class Meta(DetailSerializerV2.Meta): model = BadgeClass apispec_definition = ('BadgeClass', { 'properties': OrderedDict([ ('entityId', { 'type': "string", 'format': "string", 'description': "Unique identifier for this BadgeClass", 'readOnly': True, }), ('entityType', { 'type': "string", 'format': "string", 'description': "\"BadgeClass\"", 'readOnly': True, }), ('openBadgeId', { 'type': "string", 'format': "url", 'description': "URL of the OpenBadge compliant json", 'readOnly': True, }), ('createdAt', { 'type': 'string', 'format': 'ISO8601 timestamp', 'description': "Timestamp when the BadgeClass was created", 'readOnly': True, }), ('createdBy', { 'type': 'string', 'format': 'entityId', 'description': "BadgeUser who created this BadgeClass", 'readOnly': True, }), ('issuer', { 'type': 'string', 'format': 'entityId', 'description': "entityId of the Issuer who owns the BadgeClass", 'required': False, }), ('name', { 'type': "string", 'format': "string", 'description': "Name of the BadgeClass", 'required': True, }), ('description', { 'type': "string", 'format': "string", 'description': "Short description of the BadgeClass", 'required': True, }), ('image', { 'type': "string", 'format': "data:image/png;base64", 'description': "Base64 encoded string of an image that represents the BadgeClass.", 'required': False, }), ('criteriaUrl', { 'type': "string", 'format': "url", 'description': "External URL that describes in a human-readable format the criteria for the BadgeClass", 'required': False, }), ('criteriaNarrative', { 'type': "string", 'format': "markdown", 'description': "Markdown formatted description of the criteria", 'required': False, }), ('tags', { 'type': "array", 'items': { 'type': "string", 'format': "string" }, 'description': "List of tags that describe the BadgeClass", 'required': False, }), ('alignments', { 'type': "array", 'items': { '$ref': '#/definitions/BadgeClassAlignment' }, 'description': "List of objects describing objectives or educational standards", 'required': False, }), ('expires', { '$ref': "#/definitions/BadgeClassExpiration", 'description': "Expiration period for Assertions awarded from this BadgeClass", 'required': False, }), ]) }) def to_internal_value(self, data): if not isinstance(data, BadgeClass) and 'expires' in data: if not data['expires'] or len(data['expires']) == 0: # if expires was included blank, remove it so to_internal_value() doesnt choke del data['expires'] return super(BadgeClassSerializerV2, self).to_internal_value(data) def update(self, instance, validated_data): if 'cached_issuer' in validated_data: validated_data.pop('cached_issuer') # issuer is not updatable if 'image' in validated_data: self.context['save_kwargs'] = dict(force_resize=True) if not IsEditor().has_object_permission(self.context.get('request'), None, instance.issuer): raise serializers.ValidationError({ "issuer": "You do not have permission to edit badges on this issuer." }) return super(BadgeClassSerializerV2, self).update(instance, validated_data) def create(self, validated_data): if 'image' not in validated_data: raise serializers.ValidationError( {'image': 'Valid image file or data URI required.'}) if 'cached_issuer' in validated_data: # included issuer in request validated_data['issuer'] = validated_data.pop('cached_issuer') elif 'issuer' in self.context: # issuer was passed in context validated_data['issuer'] = self.context.get('issuer') else: # issuer is required on create raise serializers.ValidationError( {"issuer": "This field is required"}) if not IsEditor().has_object_permission( self.context.get('request'), None, validated_data['issuer']): raise serializers.ValidationError({ "issuer": "You do not have permission to edit badges on this issuer." }) return super(BadgeClassSerializerV2, self).create(validated_data)
class TestSerializer(serializers.Serializer): the_date = DateTimeWithUtcZAtEndField(source="date_field")
class TermsVersionSerializerV2(DetailSerializerV2): version = serializers.IntegerField(read_only=True) shortDescription = serializers.CharField(read_only=True, source='short_description') created = DateTimeWithUtcZAtEndField(read_only=True, source='created_at') updated = DateTimeWithUtcZAtEndField(read_only=True, source='updated_at')
class BadgeInstanceSerializerV1(OriginalJsonSerializerMixin, serializers.Serializer): created_at = DateTimeWithUtcZAtEndField(read_only=True, default_timezone=pytz.utc) created_by = BadgeUserIdentifierFieldV1(read_only=True) slug = serializers.CharField(max_length=255, read_only=True, source='entity_id') image = serializers.FileField( read_only=True) # use_url=True, might be necessary email = serializers.EmailField(max_length=1024, required=False, write_only=True) recipient_identifier = serializers.CharField(max_length=1024, required=False) recipient_type = serializers.CharField(default=RECIPIENT_TYPE_EMAIL) allow_uppercase = serializers.BooleanField(default=False, required=False, write_only=True) evidence = serializers.URLField(write_only=True, required=False, allow_blank=True, max_length=1024) narrative = MarkdownCharField(required=False, allow_blank=True, allow_null=True) evidence_items = EvidenceItemSerializer(many=True, required=False) revoked = HumanReadableBooleanField(read_only=True) revocation_reason = serializers.CharField(read_only=True) expires = DateTimeWithUtcZAtEndField(source='expires_at', required=False, allow_null=True, default_timezone=pytz.utc) create_notification = HumanReadableBooleanField(write_only=True, required=False, default=False) allow_duplicate_awards = serializers.BooleanField(write_only=True, required=False, default=True) hashed = serializers.NullBooleanField(default=None, required=False) extensions = serializers.DictField(source='extension_items', required=False, validators=[BadgeExtensionValidator()]) class Meta: apispec_definition = ('Assertion', {}) def validate(self, data): recipient_type = data.get('recipient_type') if data.get('recipient_identifier') and data.get('email') is None: if recipient_type == RECIPIENT_TYPE_EMAIL: recipient_validator = EmailValidator() elif recipient_type in (RECIPIENT_TYPE_URL, RECIPIENT_TYPE_ID): recipient_validator = URLValidator() else: recipient_validator = TelephoneValidator() try: recipient_validator(data['recipient_identifier']) except DjangoValidationError as e: raise serializers.ValidationError(e.message) elif data.get('email') and data.get('recipient_identifier') is None: data['recipient_identifier'] = data.get('email') allow_duplicate_awards = data.pop('allow_duplicate_awards') if allow_duplicate_awards is False and self.context.get( 'badgeclass') is not None: previous_awards = BadgeInstance.objects.filter( recipient_identifier=data['recipient_identifier'], badgeclass=self.context['badgeclass']).filter( Q(expires_at__isnull=True) | Q(expires_at__lt=timezone.now())) if previous_awards.exists(): raise serializers.ValidationError( "A previous award of this badge already exists for this recipient." ) hashed = data.get('hashed', None) if hashed is None: if recipient_type in (RECIPIENT_TYPE_URL, RECIPIENT_TYPE_ID): data['hashed'] = False else: data['hashed'] = True return data def validate_narrative(self, data): if data is None or data == "": return None else: return data def to_representation(self, instance): representation = super(BadgeInstanceSerializerV1, self).to_representation(instance) representation['json'] = instance.get_json(obi_version="1_1", use_canonical_id=True) if self.context.get('include_issuer', False): representation['issuer'] = IssuerSerializerV1( instance.cached_badgeclass.cached_issuer).data else: representation['issuer'] = OriginSetting.HTTP + reverse( 'issuer_json', kwargs={'entity_id': instance.cached_issuer.entity_id}) if self.context.get('include_badge_class', False): representation['badge_class'] = BadgeClassSerializerV1( instance.cached_badgeclass, context=self.context).data else: representation['badge_class'] = OriginSetting.HTTP + reverse( 'badgeclass_json', kwargs={'entity_id': instance.cached_badgeclass.entity_id}) representation['public_url'] = OriginSetting.HTTP + reverse( 'badgeinstance_json', kwargs={'entity_id': instance.entity_id}) return representation def create(self, validated_data): """ Requires self.context to include request (with authenticated request.user) and badgeclass: issuer.models.BadgeClass. """ evidence_items = [] # ob1 evidence url evidence_url = validated_data.get('evidence') if evidence_url: evidence_items.append({'evidence_url': evidence_url}) # ob2 evidence items submitted_items = validated_data.get('evidence_items') if submitted_items: evidence_items.extend(submitted_items) try: return self.context.get('badgeclass').issue( recipient_id=validated_data.get('recipient_identifier'), narrative=validated_data.get('narrative'), evidence=evidence_items, notify=validated_data.get('create_notification'), created_by=self.context.get('request').user, allow_uppercase=validated_data.get('allow_uppercase'), recipient_type=validated_data.get('recipient_type', RECIPIENT_TYPE_EMAIL), badgr_app=BadgrApp.objects.get_current( self.context.get('request')), expires_at=validated_data.get('expires_at', None), extensions=validated_data.get('extension_items', None)) except DjangoValidationError as e: raise serializers.ValidationError(e.message) def update(self, instance, validated_data): updateable_fields = [ 'evidence_items', 'expires_at', 'extension_items', 'hashed', 'narrative', 'recipient_identifier', 'recipient_type' ] for field_name in updateable_fields: if field_name in validated_data: setattr(instance, field_name, validated_data.get(field_name)) instance.rebake(save=False) instance.save() return instance
class IssuerSerializerV1(OriginalJsonSerializerMixin, serializers.Serializer): created_at = DateTimeWithUtcZAtEndField(read_only=True) created_by = BadgeUserIdentifierFieldV1() name = StripTagsCharField(max_length=1024) slug = StripTagsCharField(max_length=255, source='entity_id', read_only=True) image = ValidImageField(required=False) email = serializers.EmailField(max_length=255, required=True) description = StripTagsCharField(max_length=16384, required=False) url = serializers.URLField(max_length=1024, required=True) staff = IssuerStaffSerializerV1(read_only=True, source='cached_issuerstaff', many=True) badgrapp = serializers.CharField(read_only=True, max_length=255, source='cached_badgrapp') class Meta: apispec_definition = ('Issuer', {}) def validate_image(self, image): if image is not None: img_name, img_ext = os.path.splitext(image.name) image.name = 'issuer_logo_' + str(uuid.uuid4()) + img_ext return image def create(self, validated_data, **kwargs): user = validated_data['created_by'] potential_email = validated_data['email'] if not user.is_email_verified(potential_email): raise serializers.ValidationError( "Issuer email must be one of your verified addresses. Add this email to your profile and try again." ) new_issuer = Issuer(**validated_data) # set badgrapp new_issuer.badgrapp = BadgrApp.objects.get_current( self.context.get('request', None)) new_issuer.save() return new_issuer def update(self, instance, validated_data): force_image_resize = False instance.name = validated_data.get('name') if 'image' in validated_data: instance.image = validated_data.get('image') force_image_resize = True instance.email = validated_data.get('email') instance.description = validated_data.get('description') instance.url = validated_data.get('url') # set badgrapp if not instance.badgrapp_id: instance.badgrapp = BadgrApp.objects.get_current( self.context.get('request', None)) instance.save(force_resize=force_image_resize) return instance def to_representation(self, obj): representation = super(IssuerSerializerV1, self).to_representation(obj) representation['json'] = obj.get_json(obi_version='1_1', use_canonical_id=True) if self.context.get('embed_badgeclasses', False): representation['badgeclasses'] = BadgeClassSerializerV1( obj.badgeclasses.all(), many=True, context=self.context).data representation['badgeClassCount'] = len(obj.cached_badgeclasses()) representation['recipientGroupCount'] = len( obj.cached_recipient_groups()) representation['recipientCount'] = sum( g.member_count() for g in obj.cached_recipient_groups()) representation['pathwayCount'] = len(obj.cached_pathways()) return representation
class BackpackAssertionSerializerV2(DetailSerializerV2, OriginalJsonSerializerMixin): acceptance = serializers.ChoiceField( choices=BadgeInstance.ACCEPTANCE_CHOICES, default=BadgeInstance.ACCEPTANCE_ACCEPTED) # badgeinstance openBadgeId = serializers.URLField(source='jsonld_id', read_only=True) badgeclass = EntityRelatedFieldV2(source='cached_badgeclass', required=False, queryset=BadgeClass.cached) badgeclassOpenBadgeId = serializers.URLField(source='badgeclass_jsonld_id', read_only=True) issuer = EntityRelatedFieldV2(source='cached_issuer', required=False, queryset=Issuer.cached) issuerOpenBadgeId = serializers.URLField(source='issuer_jsonld_id', read_only=True) image = serializers.FileField(read_only=True) recipient = BadgeRecipientSerializerV2(source='*') issuedOn = DateTimeWithUtcZAtEndField(source='issued_on', read_only=True) narrative = MarkdownCharField(required=False) evidence = EvidenceItemSerializerV2(many=True, required=False) revoked = HumanReadableBooleanField(read_only=True) revocationReason = serializers.CharField(source='revocation_reason', read_only=True) expires = DateTimeWithUtcZAtEndField(source='expires_at', required=False) pending = serializers.ReadOnlyField() class Meta(DetailSerializerV2.Meta): model = BadgeInstance apispec_definition = ('BackpackAssertion', { 'properties': OrderedDict([ ('entityId', { 'type': "string", 'format': "string", 'description': "Unique identifier for this Issuer", 'readOnly': True, }), ('entityType', { 'type': "string", 'format': "string", 'description': "\"Issuer\"", 'readOnly': True, }), ('openBadgeId', { 'type': "string", 'format': "url", 'description': "URL of the OpenBadge compliant json", 'readOnly': True, }), ('badgeclass', { 'type': 'string', 'format': 'entityId', 'description': "BadgeClass that issued this Assertion", 'required': False, }), ('badgeclassOpenBadgeId', { 'type': 'string', 'format': 'url', 'description': "URL of the BadgeClass to award", 'readOnly': True, }), ('image', { 'type': 'string', 'format': 'url', 'description': "URL to the baked assertion image", 'readOnly': True, }), ('recipient', { 'type': 'object', 'properties': OrderedDict([ ('identity', { 'type': 'string', 'format': 'string', 'description': 'Either the hash of the identity or the plaintext value', 'required': True, }), ('type', { 'type': 'string', 'enum': [ c[0] for c in BadgeInstance.RECIPIENT_TYPE_CHOICES ], 'description': "Type of identifier used to identify recipient", 'required': False, }), ('hashed', { 'type': 'boolean', 'description': "Whether or not the identity value is hashed.", 'required': False, }), ('plaintextIdentity', { 'type': 'string', 'description': "The plaintext identity", 'required': False, }), ]), 'description': "Recipient that was issued the Assertion", 'required': True, }), ('issuedOn', { 'type': 'string', 'format': 'ISO8601 timestamp', 'description': "Timestamp when the Assertion was issued", 'required': True, }), ('narrative', { 'type': 'string', 'format': 'markdown', 'description': "Markdown narrative of the achievement", 'required': False, }), ('evidence', { 'type': 'array', 'items': { '$ref': '#/definitions/AssertionEvidence' }, 'description': "List of evidence associated with the achievement", 'required': False, }), ('revoked', { 'type': 'boolean', 'description': "True if this Assertion has been revoked", 'readOnly': True, }), ('revocationReason', { 'type': 'string', 'format': "string", 'description': "Short description of why the Assertion was revoked", 'readOnly': True, }), ('expires', { 'type': 'string', 'format': 'ISO8601 timestamp', 'description': "Timestamp when the Assertion expires", 'required': False, }), ]) }) def to_representation(self, instance): representation = super(BackpackAssertionSerializerV2, self).to_representation(instance) request_kwargs = self.context['kwargs'] expands = request_kwargs.get('expands', []) if self.parent is not None: # we'll have a bare representation instance_data_pointer = representation else: instance_data_pointer = representation['result'][0] if 'badgeclass' in expands: instance_data_pointer[ 'badgeclass'] = instance.cached_badgeclass.get_json( include_extra=True, use_canonical_id=True) if 'issuer' in expands: instance_data_pointer['badgeclass'][ 'issuer'] = instance.cached_issuer.get_json( include_extra=True, use_canonical_id=True) return representation