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 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 BadgeInstanceSerializerV1(OriginalJsonSerializerMixin, serializers.Serializer): created_at = serializers.DateTimeField(read_only=True) 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=BadgeInstance.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 = serializers.DateTimeField(source='expires_at', required=False, allow_null=True) create_notification = HumanReadableBooleanField(write_only=True, required=False, default=False) 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): if data.get('email') and not data.get('recipient_identifier'): data['recipient_identifier'] = data.get('email') hashed = data.get('hashed', None) if hashed is None: recipient_type = data.get('recipient_type') if recipient_type in (BadgeInstance.RECIPIENT_TYPE_URL, BadgeInstance.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): # if self.context.get('extended_json'): # self.fields['json'] = V1InstanceSerializer(source='extended_json') 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}) if apps.is_installed('badgebook'): try: from badgebook.models import BadgeObjectiveAward from badgebook.serializers import BadgeObjectiveAwardSerializer try: award = BadgeObjectiveAward.cached.get( badge_instance_id=instance.id) except BadgeObjectiveAward.DoesNotExist: representation['award'] = None else: representation['award'] = BadgeObjectiveAwardSerializer( award).data except ImportError: pass 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) 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', BadgeInstance.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)) 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.save() return instance
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 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 IssuerSerializerV2(DetailSerializerV2, OriginalJsonSerializerMixin): openBadgeId = serializers.URLField(source='jsonld_id', read_only=True) createdAt = serializers.DateTimeField(source='created_at', read_only=True) createdBy = EntityRelatedFieldV2(source='cached_creator', read_only=True) 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()]) class Meta(DetailSerializerV2.Meta): model = Issuer apispec_definition = ('Issuer', { 'properties': OrderedDict([ ('entityId', { 'type': "string", 'format': "string", 'description': "Unique identifier for this Issuer", }), ('entityType', { 'type': "string", 'format': "string", 'description': "\"Issuer\"", }), ('openBadgeId', { 'type': "string", 'format': "url", 'description': "URL of the OpenBadge compliant json", }), ('createdAt', { 'type': 'string', 'format': 'ISO8601 timestamp', 'description': "Timestamp when the Issuer was created", }), ('createdBy', { 'type': 'string', 'format': 'entityId', 'description': "BadgeUser who created this Issuer", }), ('name', { 'type': "string", 'format': "string", 'description': "Name of the Issuer", }), ('image', { 'type': "string", 'format': "data:image/png;base64", 'description': "Base64 encoded string of an image that represents the Issuer", }), ('email', { 'type': "string", 'format': "email", 'description': "Contact email for the Issuer", }), ('url', { 'type': "string", 'format': "url", 'description': "Homepage or website associated with the Issuer", }), ('description', { 'type': "string", 'format': "text", 'description': "Short description of the 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): 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 # set badgrapp new_issuer.badgrapp = BadgrApp.objects.get_current( self.context.get('request', None)) return new_issuer
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 BadgeClassSerializerV2(DetailSerializerV2, OriginalJsonSerializerMixin): openBadgeId = serializers.URLField(source='jsonld_id', read_only=True) createdAt = serializers.DateTimeField(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) 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", }), ('entityType', { 'type': "string", 'format': "string", 'description': "\"BadgeClass\"", }), ('openBadgeId', { 'type': "string", 'format': "url", 'description': "URL of the OpenBadge compliant json", }), ('createdAt', { 'type': 'string', 'format': 'ISO8601 timestamp', 'description': "Timestamp when the BadgeClass was created", }), ('createdBy', { 'type': 'string', 'format': 'entityId', 'description': "BadgeUser who created this BadgeClass", }), ('issuer', { 'type': 'string', 'format': 'entityId', 'description': "entityId of the Issuer who owns the BadgeClass", }), ('name', { 'type': "string", 'format': "string", 'description': "Name of the BadgeClass", }), ('description', { 'type': "string", 'format': "string", 'description': "Short description of the BadgeClass", }), ('image', { 'type': "string", 'format': "data:image/png;base64", 'description': "Base64 encoded string of an image that represents the BadgeClass.", }), ('criteriaUrl', { 'type': "string", 'format': "url", 'description': "External URL that describes in a human-readable format the criteria for the BadgeClass" }), ('criteriaNarrative', { 'type': "string", 'format': "markdown", 'description': "Markdown formatted description of the criteria" }), ('tags', { 'type': "array", 'items': { 'type': "string", 'format': "string" }, 'description': "List of tags that describe the BadgeClass" }), ('alignments', { 'type': "array", 'items': { '$ref': '#/definitions/BadgeClassAlignment' }, 'description': "List of objects describing objectives or educational standards" }), ]) }) def update(self, instance, validated_data): if 'cached_issuer' in validated_data: validated_data.pop('cached_issuer') # issuer is not updatable return super(BadgeClassSerializerV2, 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(BadgeClassSerializerV2, self).create(validated_data)
class BadgeInstanceSerializerV2(DetailSerializerV2, OriginalJsonSerializerMixin): openBadgeId = serializers.URLField(source='jsonld_id', read_only=True) createdAt = serializers.DateTimeField(source='created_at', read_only=True) 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) 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 = serializers.DateTimeField(source='issued_on', required=False) 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) expires = serializers.DateTimeField(source='expires_at', required=False, allow_null=True) notify = HumanReadableBooleanField(write_only=True, required=False, default=False) 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", }), ('entityType', { 'type': "string", 'format': "string", 'description': "\"Assertion\"", }), ('openBadgeId', { 'type': "string", 'format': "url", 'description': "URL of the OpenBadge compliant json", }), ('createdAt', { 'type': 'string', 'format': 'ISO8601 timestamp', 'description': "Timestamp when the Assertion was created", }), ('createdBy', { 'type': 'string', 'format': 'entityId', 'description': "BadgeUser who created the Assertion", }), ('badgeclass', { 'type': 'string', 'format': 'entityId', 'description': "BadgeClass that issued this Assertion", }), ('badgeclassOpenBadgeId', { 'type': 'string', 'format': 'url', 'description': "URL of the BadgeClass to award", }), ('revoked', { 'type': 'boolean', 'description': "True if this Assertion has been revoked", }), ('revocationReason', { 'type': 'string', 'format': "string", 'description': "Short description of why the Assertion was revoked", }), ('image', { 'type': 'string', 'format': 'url', 'description': "URL to the baked assertion image", }), ('issuedOn', { 'type': 'string', 'format': 'ISO8601 timestamp', 'description': "Timestamp when the Assertion was issued", }), ('narrative', { 'type': 'string', 'format': 'markdown', 'description': "Markdown narrative of the achievement", }), ('evidence', { 'type': 'array', 'items': { '$ref': '#/definitions/AssertionEvidence' }, 'description': "List of evidence associated with the achievement" }), ('recipient', { 'type': 'object', 'properties': OrderedDict([ ('identity', { 'type': 'string', 'format': 'string', 'description': 'Either the hash of the identity or the plaintext value' }), ('type', { 'type': 'string', 'enum': [ c[0] for c in BadgeInstance.RECIPIENT_TYPE_CHOICES ], 'description': "Type of identifier used to identify recipient" }), ('hashed', { 'type': 'boolean', 'description': "Whether or not the identity value is hashed." }), ('plaintextIdentity', { 'type': 'string', 'description': "The plaintext identity" }), ]), 'description': "Recipient that was issued the Assertion" }), ('expires', { 'type': 'string', 'format': 'ISO8601 timestamp', 'description': "Timestamp when the Assertion expires", }), ]) }) 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.save() instance.rebake() return instance def validate(self, data): request = self.context.get('request', None) 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_identifier': ["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') else: raise serializers.ValidationError( {"badgeclass": ["This field is required"]}) expected_issuer = self.context.get('kwargs', {}).get('issuer') if expected_issuer and data['badgeclass'].issuer != expected_issuer: raise serializers.ValidationError({ "badgeclass": ["Could not find matching badgeclass for this issuer."] }) if 'badgeclass' in data: data['issuer'] = data['badgeclass'].issuer return data
class BadgeInstanceSerializer(OriginalJsonSerializerMixin, serializers.Serializer): allow_uppercase = serializers.BooleanField(default=False, required=False, write_only=True) issue_signed = serializers.BooleanField(required=False) signing_password = serializers.CharField(max_length=1024, required=False) enrollment_entity_id = serializers.CharField(max_length=1024, required=False) extensions = serializers.DictField(source='extension_items', required=False, validators=[BadgeExtensionValidator()]) narrative = MarkdownCharField(required=False, allow_blank=True, allow_null=True) evidence_items = EvidenceItemSerializer(many=True, required=False) def get_recipient_email(self, obj): return obj.get_email_address() def get_recipient_name(self, obj): return obj.get_recipient_name() def validate(self, data): badgeclass = self.context['request'].data.get('badgeclass') if badgeclass.narrative_required and not data.get('narrative'): raise BadgrValidationError(error_code=999, error_message="Narrative is required") if badgeclass.evidence_required and not data.get("evidence_items"): raise BadgrValidationError(error_code=999, error_message="Eviidence is required") if data.get('email') and not data.get('recipient_identifier'): data['recipient_identifier'] = data.get('email') hashed = data.get('hashed', None) if hashed is None: recipient_type = data.get('recipient_type') if recipient_type in (BadgeInstance.RECIPIENT_TYPE_URL, BadgeInstance.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(BadgeInstanceSerializer, 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'] = IssuerSerializer( 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'] = BadgeClassSerializer( 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}) if apps.is_installed('badgebook'): try: from badgebook.models import BadgeObjectiveAward from badgebook.serializers import BadgeObjectiveAwardSerializer try: award = BadgeObjectiveAward.cached.get( badge_instance_id=instance.id) except BadgeObjectiveAward.DoesNotExist: representation['award'] = None else: representation['award'] = BadgeObjectiveAwardSerializer( award).data except ImportError: pass return representation def create(self, validated_data): """ Requires self.context to include request (with authenticated request.user) and badgeclass: issuer.models.BadgeClass. """ badgeclass = self.context['request'].data.get('badgeclass') enrollment = StudentsEnrolled.objects.get( entity_id=validated_data.get('enrollment_entity_id')) expires_at = None if badgeclass.expiration_period: expires_at = datetime.datetime.now().replace( microsecond=0, second=0, minute=0, hour=0) + badgeclass.expiration_period if enrollment.badge_instance: raise BadgrValidationError( "Can't award enrollment, it has already been awarded", 213) if self.context['request'].data.get('issue_signed', False): assertion = badgeclass.issue_signed( recipient=enrollment.user, created_by=self.context.get('request').user, allow_uppercase=validated_data.get('allow_uppercase'), recipient_type=validated_data.get( 'recipient_type', BadgeInstance.RECIPIENT_TYPE_EDUID), expires_at=expires_at, extensions=validated_data.get('extension_items', None), identifier=uuid.uuid4().urn, signer=validated_data.get('created_by'), # evidence=validated_data.get('evidence_items', None) # Dont forget this one when you re-implement signing # narrative=validated_data.get('narrative', None) # idem ) else: assertion = badgeclass.issue( recipient=enrollment.user, created_by=self.context.get('request').user, allow_uppercase=validated_data.get('allow_uppercase'), recipient_type=validated_data.get( 'recipient_type', BadgeInstance.RECIPIENT_TYPE_EDUID), expires_at=expires_at, extensions=validated_data.get('extension_items', None), evidence=validated_data.get('evidence_items', None), narrative=validated_data.get('narrative', None)) enrollment.date_awarded = timezone.now() enrollment.badge_instance = assertion enrollment.save() enrollment.user.remove_cached_data(['cached_pending_enrollments']) # delete the pending direct awards for this badgeclass and this user badgeclass.cached_pending_direct_awards().filter( eppn__in=enrollment.user.eppns).delete() return assertion
class IssuerSerializer(OriginalJsonSerializerMixin, ExtensionsSaverMixin, InternalValueErrorOverrideMixin, serializers.Serializer): created_at = serializers.DateTimeField(read_only=True) created_by = BadgeUserIdentifierField() name_english = StripTagsCharField(max_length=1024, required=False, allow_null=True, allow_blank=True) name_dutch = StripTagsCharField(max_length=1024, required=False, allow_null=True, allow_blank=True) entity_id = StripTagsCharField(max_length=255, read_only=True) image_english = ValidImageField(required=False) image_dutch = ValidImageField(required=False) email = serializers.EmailField(max_length=255, required=True) description_english = StripTagsCharField(max_length=16384, required=False, allow_null=True, allow_blank=True) description_dutch = StripTagsCharField(max_length=16384, required=False, allow_null=True, allow_blank=True) url_english = serializers.URLField(max_length=1024, required=False, allow_null=True, allow_blank=True) url_dutch = serializers.URLField(max_length=1024, required=False, allow_null=True, allow_blank=True) faculty = FacultySlugRelatedField(slug_field='entity_id', required=True) extensions = serializers.DictField(source='extension_items', required=False, validators=[BadgeExtensionValidator()]) def _validate_image(self, image): img_name, img_ext = os.path.splitext(image.name) image.name = 'issuer_logo_' + str(uuid.uuid4()) + img_ext image = resize_image(image) app = BadgrApp.objects.get_current(self.context.get('request', None)) if app.is_demo_environment: image = add_watermark(image) if verify_svg(image): image = scrub_svg_image(image) return image def validate_image_english(self, image_english): if image_english is not None: image_english = self._validate_image(image_english) return image_english def validate_image_dutch(self, image_dutch): if image_dutch is not None: image_dutch = self._validate_image(image_dutch) return image_dutch def create(self, validated_data, **kwargs): user_permissions = validated_data['faculty'].get_permissions( validated_data['created_by']) if user_permissions['may_create']: 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 else: raise BadgrValidationError( "You don't have the necessary permissions", 100) def update(self, instance, validated_data): if instance.assertions and instance.name_english and instance.name_english != validated_data[ "name_english"]: raise BadgrValidationError( "Cannot change the name, assertions have already been issued within this entity", 214) if instance.assertions and instance.name_dutch and instance.name_dutch != validated_data[ "name_dutch"]: raise BadgrValidationError( "Cannot change the name, assertions have already been issued within this entity", 214) [ setattr(instance, attr, validated_data.get(attr)) for attr in validated_data ] self.save_extensions(validated_data, instance) if not instance.badgrapp_id: instance.badgrapp = BadgrApp.objects.get_current( self.context.get('request', None)) instance.save() return instance def to_representation(self, obj): representation = super(IssuerSerializer, 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'] = BadgeClassSerializer( obj.badgeclasses.all(), many=True, context=self.context).data if not representation['image_english']: representation['image_english'] = obj.institution.image_url() if not representation['image_dutch']: representation['image_dutch'] = obj.institution.image_url() return representation def to_internal_value_error_override(self, data): """Function used in combination with the InternalValueErrorOverrideMixin to override serializer exceptions before the instance is saved (i.e. the save() method is called)""" errors = OrderedDict() institution = self.context['request'].user.institution if institution.default_language == institution.DEFAULT_LANGUAGE_DUTCH: if not data.get('name_dutch', False): e = OrderedDict([ ('name_dutch', [ErrorDetail('Dutch name is required', code=912)]) ]) errors = OrderedDict(chain(errors.items(), e.items())) if not data.get('description_dutch', False): e = OrderedDict([ ('description_dutch', [ErrorDetail('Dutch description is required', code=913)]) ]) errors = OrderedDict(chain(errors.items(), e.items())) if not data.get('url_dutch', False): e = OrderedDict([ ('url_dutch', [ErrorDetail('Dutch url is required', code=915)]) ]) errors = OrderedDict(chain(errors.items(), e.items())) if institution.default_language == institution.DEFAULT_LANGUAGE_ENGLISH: if not data.get('name_english', False): e = OrderedDict([ ('name_english', [ErrorDetail('English name is required', code=924)]) ]) errors = OrderedDict(chain(errors.items(), e.items())) if not data.get('description_english', False): e = OrderedDict([('description_english', [ ErrorDetail('English description is required', code=925) ])]) errors = OrderedDict(chain(errors.items(), e.items())) if not data.get('url_english', False): e = OrderedDict([ ('url_english', [ErrorDetail('English url is required', code=923)]) ]) errors = OrderedDict(chain(errors.items(), e.items())) return errors def add_extensions(self, instance, add_these_extensions, extension_items): for extension_name in add_these_extensions: original_json = extension_items[extension_name] extension = IssuerExtension( name=extension_name, original_json=json.dumps(original_json), issuer_id=instance.pk) extension.save()
class BadgeClassSerializer(OriginalJsonSerializerMixin, ExtensionsSaverMixin, InternalValueErrorOverrideMixin, serializers.Serializer): created_at = serializers.DateTimeField(read_only=True) created_by = BadgeUserIdentifierField() name = StripTagsCharField(max_length=255) image = ValidImageField(required=True) formal = serializers.BooleanField(required=True) is_private = serializers.BooleanField(required=False, default=False) narrative_required = serializers.BooleanField(required=False, default=False) evidence_required = serializers.BooleanField(required=False, default=False) entity_id = StripTagsCharField(max_length=255, read_only=True) issuer = IssuerSlugRelatedField(slug_field='entity_id', required=True) 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()]) description = StripTagsCharField(max_length=16384, required=True, convert_null=True) alignments = AlignmentItemSerializer(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()]) expiration_period = PeriodField(required=False) award_allowed_institutions = PrimaryKeyRelatedField( many=True, queryset=Institution.objects.all(), required=False) class Meta: apispec_definition = ('BadgeClass', {}) def get_expiration_period(self, instance): if instance.expiration_period: return instance.expiration_period.days def to_representation(self, instance): representation = super(BadgeClassSerializer, 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 image = resize_image(image) app = BadgrApp.objects.get_current( self.context.get('request', None)) if app.is_demo_environment: image = add_watermark(image) if verify_svg(image): image = scrub_svg_image(image) 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_name(self, name): return strip_tags(name) def validate_description(self, description): return strip_tags(description) 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'): 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): if instance.assertions and len( [ass for ass in instance.assertions if not ass.revoked]) > 0: raise BadgrValidationError( error_code=999, error_message= "Cannot change any value, assertions have already been issued") self.save_extensions(validated_data, instance) for key, value in validated_data.items(): if key is not 'award_allowed_institutions': setattr(instance, key, value) instance.award_allowed_institutions.set( validated_data.get('award_allowed_institutions', [])) try: instance.save() except IntegrityError: raise BadgrValidationFieldError( 'name', "There is already a Badgeclass with this name inside this Issuer", 911) return instance def validate_alignments(self, alignments): alignment_max = 8 if alignments.__len__() >= alignment_max: raise BadgrValidationFieldError( 'alignments', "There are too many Related educational framework objects, the maximum is {}." .format(str(alignment_max), 922)) return alignments def to_internal_value_error_override(self, data): """Function used in combination with the InternalValueErrorOverrideMixin to override serializer excpetions when data is internalised (i.e. the to_internal_value() method is called)""" errors = OrderedDict() if not data.get('criteria_text', False) and not data.get( 'criteria_url', False): e = OrderedDict([ ('criteria_text', [ ErrorDetail( 'Either criteria_url or criteria_text is required', code=905) ]), ('criteria_url', [ ErrorDetail( 'Either criteria_url or criteria_text is required', code=905) ]) ]) errors = OrderedDict(chain(errors.items(), e.items())) if data.get('criteria_url', False): if not utils.is_probable_url(data.get('criteria_url')): e = OrderedDict([ ('criteria_url', [ErrorDetail('Must be a proper url.', code=902)]) ]) errors = OrderedDict(chain(errors.items(), e.items())) if data.get('name') == settings.EDUID_BADGE_CLASS_NAME: e = OrderedDict([('name', [ ErrorDetail( f"{settings.EDUID_BADGE_CLASS_NAME} is a reserved name for badgeclasses", code=907) ])]) errors = OrderedDict(chain(errors.items(), e.items())) return errors def create(self, validated_data, **kwargs): user_permissions = validated_data['issuer'].get_permissions( validated_data['created_by']) if user_permissions['may_create']: if validated_data['formal'] and not validated_data[ 'issuer'].faculty.institution.grondslag_formeel: raise BadgrValidationError( "Cannot create a formal badgeclass for an institution without the judicial basis for formal badges", 215) if not validated_data['formal'] and not validated_data[ 'issuer'].faculty.institution.grondslag_informeel: raise BadgrValidationError( "Cannot create an informal badgeclass for an institution without the judicial basis for informal badges", 216) try: new_badgeclass = BadgeClass.objects.create(**validated_data) except IntegrityError: raise BadgrValidationFieldError( 'name', "There is already a Badgeclass with this name inside this Issuer", 911) return new_badgeclass else: raise BadgrValidationError( "You don't have the necessary permissions", 100)
class IssuerSerializerV1(OriginalJsonSerializerMixin, ExtensionsSaverMixin, serializers.Serializer): created_at = serializers.DateTimeField(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) faculty = FacultySerializerV1(required=False, allow_null=True) extensions = serializers.DictField(source='extension_items', required=False, validators=[BadgeExtensionValidator()]) 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): if self.context['request'].data.get('faculty', None): faculty_id = self.context['request'].data['faculty']['id'] faculty = Faculty.objects.get(pk=faculty_id) validated_data['faculty'] = faculty 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): if self.context['request'].data['faculty']: faculty_id = self.context['request'].data['faculty']['id'] faculty = Faculty.objects.get(pk=faculty_id) validated_data['faculty'] = faculty instance.name = validated_data.get('name') if 'image' in validated_data: instance.image = validated_data.get('image') instance.email = validated_data.get('email') instance.description = validated_data.get('description') instance.url = validated_data.get('url') instance.faculty = validated_data.get('faculty') # set badgrapp if not instance.badgrapp_id: instance.badgrapp = BadgrApp.objects.get_current( self.context.get('request', None)) self.save_extensions(validated_data, instance) instance.save() 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 def add_extensions(self, instance, add_these_extensions, extension_items): for extension_name in add_these_extensions: original_json = extension_items[extension_name] extension = IssuerExtension( name=extension_name, original_json=json.dumps(original_json), issuer_id=instance.pk) extension.save()
class BadgeClassSerializerV1(OriginalJsonSerializerMixin, ExtensionsSaverMixin, serializers.Serializer): created_at = serializers.DateTimeField(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) enrollment_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) extensions = serializers.DictField(source='extension_items', required=False, validators=[BadgeExtensionValidator()]) class Meta: apispec_definition = ('BadgeClass', {}) 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 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): 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') instance.alignment_items = validated_data.get('alignment_items') instance.tag_items = validated_data.get('tag_items') self.save_extensions(validated_data, instance) instance.save() 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, unicode)): 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 filter_extensions(self, validated_data): extension_items = validated_data['extension_items'] for extension_name in extension_items.keys(): extension_item = extension_items[extension_name] if extension_name == 'languageExtension': del extension_item['typedLanguage'] 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') self.filter_extensions(validated_data) new_badgeclass = BadgeClass.objects.create(**validated_data) return new_badgeclass
class IssuerSerializer(OriginalJsonSerializerMixin, ExtensionsSaverMixin, InternalValueErrorOverrideMixin, serializers.Serializer): created_at = serializers.DateTimeField(read_only=True) created_by = BadgeUserIdentifierField() name = StripTagsCharField(max_length=1024) entity_id = StripTagsCharField(max_length=255, read_only=True) image = ValidImageField(required=False) email = serializers.EmailField(max_length=255, required=True) description_english = StripTagsCharField(max_length=16384, required=False) description_dutch = StripTagsCharField(max_length=16384, required=False) url = serializers.URLField(max_length=1024, required=True) faculty = FacultySlugRelatedField(slug_field='entity_id', required=True) extensions = serializers.DictField(source='extension_items', required=False, validators=[BadgeExtensionValidator()]) 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_permissions = validated_data['faculty'].get_permissions( validated_data['created_by']) if user_permissions['may_create']: new_issuer = Issuer(**validated_data) # set badgrapp new_issuer.badgrapp = BadgrApp.objects.get_current( self.context.get('request', None)) try: new_issuer.save() except IntegrityError: raise BadgrValidationFieldError( 'name', "There is already an Issuer with this name inside this Issuer group", 908) return new_issuer else: raise BadgrValidationError( "You don't have the necessary permissions", 100) def update(self, instance, validated_data): if instance.assertions and instance.name != validated_data["name"]: raise BadgrValidationError( "Cannot change the name, assertions have already been issued within this entity", 214) [ setattr(instance, attr, validated_data.get(attr)) for attr in validated_data ] self.save_extensions(validated_data, instance) if not instance.badgrapp_id: instance.badgrapp = BadgrApp.objects.get_current( self.context.get('request', None)) try: instance.save() except IntegrityError: raise BadgrValidationFieldError( 'name', "There is already an Issuer with this name inside this Issuer group", 908) return instance def to_representation(self, obj): representation = super(IssuerSerializer, 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'] = BadgeClassSerializer( obj.badgeclasses.all(), many=True, context=self.context).data if not representation['image']: representation['image'] = obj.institution.image_url() return representation def to_internal_value_error_override(self, data): """Function used in combination with the InternalValueErrorOverrideMixin to override serializer excpetions when data is internalised (i.e. the to_internal_value() method is called)""" errors = OrderedDict() if data.get('email', False): try: EmailValidator().__call__(data.get('email')) except ValidationError: e = OrderedDict([ ('email', [ErrorDetail('Enter a valid email address.', code=509)]) ]) errors = OrderedDict(chain(errors.items(), e.items())) return errors def add_extensions(self, instance, add_these_extensions, extension_items): for extension_name in add_these_extensions: original_json = extension_items[extension_name] extension = IssuerExtension( name=extension_name, original_json=json.dumps(original_json), issuer_id=instance.pk) extension.save()
class BadgeInstanceSerializer(OriginalJsonSerializerMixin, serializers.Serializer): created_at = serializers.DateTimeField(read_only=True) created_by = BadgeUserIdentifierField(read_only=True) entity_id = serializers.CharField(max_length=255, read_only=True) 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_email = serializers.SerializerMethodField() recipient_name = serializers.SerializerMethodField() recipient_type = serializers.CharField( default=BadgeInstance.RECIPIENT_TYPE_EDUID) allow_uppercase = serializers.BooleanField(default=False, required=False, write_only=True) revoked = HumanReadableBooleanField(read_only=True) revocation_reason = serializers.CharField(read_only=True) expires = serializers.DateTimeField(source='expires_at', required=False, allow_null=True) issue_signed = serializers.BooleanField(required=False) signing_password = serializers.CharField(max_length=1024, required=False) enrollment_entity_id = serializers.CharField(max_length=1024, required=False) create_notification = HumanReadableBooleanField(write_only=True, required=False, default=False) hashed = serializers.NullBooleanField(default=None, required=False) extensions = serializers.DictField(source='extension_items', required=False, validators=[BadgeExtensionValidator()]) class Meta: apispec_definition = ('Assertion', {}) def get_recipient_email(self, obj): return obj.get_email_address() def get_recipient_name(self, obj): return obj.get_recipient_name() def validate(self, data): if data.get('email') and not data.get('recipient_identifier'): data['recipient_identifier'] = data.get('email') hashed = data.get('hashed', None) if hashed is None: recipient_type = data.get('recipient_type') if recipient_type in (BadgeInstance.RECIPIENT_TYPE_URL, BadgeInstance.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): # if self.context.get('extended_json'): # self.fields['json'] = V1InstanceSerializer(source='extended_json') representation = super(BadgeInstanceSerializer, 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'] = IssuerSerializer( 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'] = BadgeClassSerializer( 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}) if apps.is_installed('badgebook'): try: from badgebook.models import BadgeObjectiveAward from badgebook.serializers import BadgeObjectiveAwardSerializer try: award = BadgeObjectiveAward.cached.get( badge_instance_id=instance.id) except BadgeObjectiveAward.DoesNotExist: representation['award'] = None else: representation['award'] = BadgeObjectiveAwardSerializer( award).data except ImportError: pass return representation def create(self, validated_data): """ Requires self.context to include request (with authenticated request.user) and badgeclass: issuer.models.BadgeClass. """ badgeclass = self.context['request'].data.get('badgeclass') enrollment = StudentsEnrolled.objects.get( entity_id=validated_data.get('enrollment_entity_id')) expires_at = None if badgeclass.expiration_period: expires_at = datetime.datetime.now().replace( microsecond=0, second=0, minute=0, hour=0) + badgeclass.expiration_period if enrollment.badge_instance: raise BadgrValidationError( "Can't award enrollment, it has already been awarded", 213) if self.context['request'].data.get('issue_signed', False): assertion = badgeclass.issue_signed( recipient=enrollment.user, created_by=self.context.get('request').user, allow_uppercase=validated_data.get('allow_uppercase'), recipient_type=validated_data.get( 'recipient_type', BadgeInstance.RECIPIENT_TYPE_EDUID), expires_at=expires_at, extensions=validated_data.get('extension_items', None), identifier=uuid.uuid4().urn, signer=validated_data.get('created_by'), ) else: assertion = badgeclass.issue( recipient=enrollment.user, created_by=self.context.get('request').user, allow_uppercase=validated_data.get('allow_uppercase'), recipient_type=validated_data.get( 'recipient_type', BadgeInstance.RECIPIENT_TYPE_EDUID), expires_at=expires_at, extensions=validated_data.get('extension_items', None)) enrollment.date_awarded = timezone.now() enrollment.badge_instance = assertion enrollment.save() enrollment.user.remove_cached_data(['cached_pending_enrollments']) return assertion def update(self, instance, validated_data): updateable_fields = [ 'evidence_items', 'expires_at', 'extension_items', 'hashed', 'issued_on', '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