class ProjectSerializer(NestedModelSerializer, ContentTypeAnnotatedModelSerializer, GetCurrentUserAnnotatedSerializerMixin): user = SimplestUserSerializer(required=False, read_only=True, default=CreateOnlyCurrentUserDefault()) owner = SimplestUserSerializer(required=False, allow_null=True) pm = SimplestUserSerializer(required=False, allow_null=True) skills = SimpleSkillSerializer(required=False, many=True) participation = SimpleParticipationSerializer(required=False, many=True, source='participation_set') documents = SimpleDocumentSerializer(required=False, many=True, source='document_set') progress_events = SimpleProgressEventSerializer(required=False, many=True, source='progressevent_set') meta = SimpleProjectMetaSerializer(required=False, many=True, source='projectmeta_set') change_log = serializers.JSONField(required=False, write_only=True) margin = serializers.ReadOnlyField() interest_polls = SimpleInterestPollSerializer(required=False, many=True, source='interestpoll_set') class Meta: model = Project fields = '__all__' read_only_fields = ('created_at', 'updated_at') def nested_save_override(self, validated_data, instance=None): initial_stage = None if instance: initial_stage = instance.stage instance = super(ProjectSerializer, self).nested_save_override(validated_data, instance=instance) if instance and initial_stage != instance.stage: post_field_update.send(sender=Project, instance=instance, field='stage') return instance def save_nested_skills(self, data, instance, created=False): if data is not None: instance.skills = ', '.join( [skill.get('name', '') for skill in data]) instance.save() def save_nested_change_log(self, data, instance, created=False): if data is not None: for item in data: FieldChangeLog.objects.create( content_object=instance, created_by=self.get_current_user(), **item)
class ProgressReportSerializer(NestedModelSerializer, ContentTypeAnnotatedModelSerializer, GetCurrentUserAnnotatedSerializerMixin): user = SimplestUserSerializer(required=False, read_only=True, default=CreateOnlyCurrentUserDefault()) event = NestedProgressEventSerializer() status_display = serializers.CharField(required=False, read_only=True, source='get_status_display') stuck_reason_display = serializers.CharField( required=False, read_only=True, source='get_stuck_reason_display') class Meta: model = ProgressReport fields = '__all__' read_only_fields = ('created_at', 'updated_at') def validate(self, attrs): errors = dict() current_user = self.get_current_user() if current_user.is_authenticated(): BOOLEANS = (True, False) required_fields = [] status_schema = ('status', [ status_item[0] for status_item in PROGRESS_REPORT_STATUS_CHOICES ], [([ PROGRESS_REPORT_STATUS_BEHIND_AND_STUCK, PROGRESS_REPORT_STATUS_STUCK ], 'stuck_reason', [ stuck_reason_item[0] for stuck_reason_item in PROGRESS_REPORT_STUCK_REASON_CHOICES ])]) rate_deliverables_schema = ('rate_deliverables', list(range(1, 6)) ) # 1...5 if current_user.is_developer: required_fields = [ status_schema, 'started_at', ('percentage', list(range(0, 101))), # 0...100 'accomplished', rate_deliverables_schema, 'todo', 'next_deadline', ('next_deadline_meet', BOOLEANS, [(False, 'next_deadline_fail_reason')]) ] elif current_user.is_project_manager: required_fields = [ status_schema, ('last_deadline_met', BOOLEANS, [ (False, 'deadline_miss_communicated', BOOLEANS), (False, 'deadline_report') ]), 'percentage', 'accomplished', 'todo', 'next_deadline', ('next_deadline_meet', BOOLEANS, [(False, 'next_deadline_fail_reason')]), 'team_appraisal' ] elif current_user.is_project_owner: required_fields = [('last_deadline_met', BOOLEANS, [ (False, 'deadline_miss_communicated', BOOLEANS) ]), ('deliverable_satisfaction', BOOLEANS), rate_deliverables_schema, ('pm_communication', BOOLEANS)] errors.update( validate_field_schema(required_fields, attrs, raise_exception=False)) if errors: raise ValidationError(errors) return attrs
class SocialPlatformSerializer(serializers.ModelSerializer): created_by = serializers.HiddenField(required=False, default=CreateOnlyCurrentUserDefault()) class Meta: model = SocialPlatform exclude = ('created_at',)
class SocialLinkSerializer(DetailAnnotatedModelSerializer): user = serializers.PrimaryKeyRelatedField(required=False, read_only=True, default=CreateOnlyCurrentUserDefault()) class Meta: model = SocialLink exclude = ('created_at',) details_serializer = SocialLinkDetailsSerializer
class ConnectionSerializer(DetailAnnotatedModelSerializer): from_user = serializers.PrimaryKeyRelatedField(required=False, read_only=True, default=CreateOnlyCurrentUserDefault()) class Meta: model = Connection exclude = ('created_at',) details_serializer = ConnectionDetailsSerializer
class ProfileSerializer(DetailAnnotatedModelSerializer): user = serializers.PrimaryKeyRelatedField(required=False, read_only=True, default=CreateOnlyCurrentUserDefault()) city = serializers.CharField(required=False, allow_blank=True, allow_null=True) skills = serializers.CharField(required=False, allow_blank=True, allow_null=True) country = CountryField(required=False) class Meta: model = UserProfile details_serializer = ProfileDetailsSerializer def create(self, validated_data): skills = None city = None if 'skills' in validated_data: skills = validated_data.pop('skills') if 'city' in validated_data: city = validated_data.pop('city') instance = super(ProfileSerializer, self).create(validated_data) self.save_skills(instance, skills) self.save_city(instance, city) return instance def update(self, instance, validated_data): skills = None city = None if 'skills' in validated_data: skills = validated_data.pop('skills') if 'city' in validated_data: city = validated_data.pop('city') instance = super(ProfileSerializer, self).update(instance, validated_data) self.save_skills(instance, skills) self.save_city(instance, city) return instance def save_skills(self, profile, skills): if skills is not None: profile.skills = skills profile.save() def save_city(self, profile, city): if city: profile.city = city profile.save()
class TaskSerializer(ContentTypeAnnotatedSerializer, DetailAnnotatedSerializer): user = serializers.PrimaryKeyRelatedField( required=False, read_only=True, default=CreateOnlyCurrentUserDefault()) display_fee = serializers.SerializerMethodField(required=False, read_only=True) excerpt = serializers.CharField(required=False, read_only=True) skills = serializers.CharField(required=True, allow_blank=True, allow_null=True) deadline = serializers.DateTimeField(required=False, allow_null=True) can_apply = serializers.SerializerMethodField(read_only=True, required=False) can_save = serializers.SerializerMethodField(read_only=True, required=False) is_participant = serializers.SerializerMethodField(read_only=True, required=False) my_participation = serializers.SerializerMethodField(read_only=True, required=False) summary = serializers.CharField(read_only=True, required=False) assignee = serializers.SerializerMethodField(required=False, read_only=True) participants = serializers.PrimaryKeyRelatedField( many=True, queryset=get_user_model().objects.all(), required=False, write_only=True) milestones = MilestoneSerializer(many=True) open_applications = serializers.SerializerMethodField(required=False, read_only=True) update_schedule_display = serializers.SerializerMethodField(required=False, read_only=True) participation = NestedTaskParticipationSerializer( required=False, many=True, source='participation_set') class Meta: model = Task exclude = ('applicants', ) read_only_fields = ('created_at', ) details_serializer = TaskDetailsSerializer def create(self, validated_data): skills = None participation = None participants = None if 'skills' in validated_data: skills = validated_data.pop('skills') if 'participation_set' in validated_data: participation = validated_data.pop('participation_set') if 'participants' in validated_data: participants = validated_data.pop('participants') instance = super(TaskSerializer, self).create(validated_data) self.save_skills(instance, skills) self.save_participants(instance, participants) self.save_participation(instance, participation) # Triggered here instead of in the post_save signal to allow skills to be attached first # TODO: Consider moving this trigger send_new_task_email(instance) return instance def update(self, instance, validated_data): initial_apply = instance.apply skills = None participation = None participants = None if 'skills' in validated_data: skills = validated_data.pop('skills') if 'participation_set' in validated_data: participation = validated_data.pop('participation_set') if 'participants' in validated_data: participants = validated_data.pop('participants') instance = super(TaskSerializer, self).update(instance, validated_data) self.save_skills(instance, skills) self.save_participants(instance, participants) self.save_participation(instance, participation) # TODO: Consider moving this trigger if initial_apply and not instance.apply: send_task_application_not_accepted_email(instance) return instance def save_skills(self, task, skills): if skills is not None: task.skills = skills task.save() def save_participation(self, task, participation): if participation: new_assignee = None for item in participation: try: Participation.objects.update_or_create(task=task, user=item['user'], defaults=item) if 'assignee' in item and item['assignee']: new_assignee = item['user'] except: pass if new_assignee: Participation.objects.exclude(user=new_assignee).filter( task=task).update(assignee=False) def save_participants(self, task, participants): # TODO: Remove and move existing code to using save_participation if participants: assignee = self.initial_data.get('assignee', None) confirmed_participants = self.initial_data.get( 'confirmed_participants', None) rejected_participants = self.initial_data.get( 'rejected_participants', None) created_by = self.__get_current_user() if not created_by: created_by = task.user changed_assignee = False for user in participants: try: defaults = {'created_by': created_by} if assignee: defaults['assignee'] = bool(user.id == assignee) if rejected_participants and user.id in rejected_participants: defaults['accepted'] = False defaults['responded'] = True if confirmed_participants and user.id in confirmed_participants: defaults['accepted'] = True defaults['responded'] = True Participation.objects.update_or_create(task=task, user=user, defaults=defaults) if user.id == assignee: changed_assignee = True except: pass if assignee and changed_assignee: Participation.objects.exclude(user__id=assignee).filter( task=task).update(assignee=False) def __get_current_user(self): request = self.context.get("request", None) if request: return getattr(request, "user", None) return None def get_display_fee(self, obj): user = self.__get_current_user() amount = None if user and user.is_developer: amount = obj.fee * (1 - TUNGA_SHARE_PERCENTAGE * 0.01) return obj.display_fee(amount=amount) def get_can_apply(self, obj): if obj.closed or not obj.apply: return False request = self.context.get("request", None) if request: user = getattr(request, "user", None) if user: if obj.user == user: return False return obj.applicants.filter(id=user.id).count() == 0 and \ obj.participation_set.filter(user=user).count() == 0 return False def get_can_save(self, obj): request = self.context.get("request", None) if request: user = getattr(request, "user", None) if user: if obj.user == user: return False return obj.savedtask_set.filter(user=user).count() == 0 return False def get_is_participant(self, obj): request = self.context.get("request", None) if request: user = getattr(request, "user", None) if user: return obj.participation_set.filter( (Q(accepted=True) | Q(responded=False)), user=user).count() == 1 return False def get_my_participation(self, obj): request = self.context.get("request", None) if request: user = getattr(request, "user", None) if user: try: participation = obj.participation_set.get(user=user) return { 'id': participation.id, 'user': participation.user.id, 'assignee': participation.assignee, 'accepted': participation.accepted, 'responded': participation.responded } except: pass return None def get_assignee(self, obj): try: assignee = obj.participation_set.get( (Q(accepted=True) | Q(responded=False)), assignee=True) return { 'user': assignee.user.id, 'accepted': assignee.accepted, 'responded': assignee.responded } except: return None def get_open_applications(self, obj): return obj.application_set.filter(responded=False).count() def get_update_schedule_display(self, obj): if obj.update_interval and obj.update_interval_units: if obj.update_interval == 1 and obj.update_interval_units == UPDATE_SCHEDULE_DAILY: return 'Daily' interval_units = str( obj.get_update_interval_units_display()).lower() if obj.update_interval == 1: return 'Every %s' % interval_units return 'Every %s %ss' % (obj.update_interval, interval_units) return None
class ChannelSerializer(DetailAnnotatedModelSerializer, GetCurrentUserAnnotatedSerializerMixin): created_by = SimpleUserSerializer(required=False, read_only=True, default=CreateOnlyCurrentUserDefault()) display_type = serializers.CharField(required=False, read_only=True, source='get_type_display') participants = serializers.PrimaryKeyRelatedField( required=True, many=True, queryset=get_user_model().objects.all()) attachments = UploadSerializer(read_only=True, required=False, many=True, source='all_attachments') user = serializers.SerializerMethodField(read_only=True, required=False) new_messages = serializers.IntegerField(read_only=True, required=False) new = serializers.SerializerMethodField(read_only=True, required=False) last_read = serializers.SerializerMethodField(read_only=True, required=False) alt_subject = serializers.CharField(required=False, read_only=True, source='get_alt_subject') class Meta: model = Channel exclude = () read_only_fields = ('created_at', 'type') details_serializer = ChannelDetailsSerializer def validate_participants(self, value): error = 'Select some participants for this conversation' if not isinstance(value, list) or not value: raise ValidationError(error) participants = self.clean_participants(value) if not participants: raise ValidationError(error) return value def create(self, validated_data): participants = None if 'participants' in validated_data: participants = validated_data.pop('participants') participants = self.clean_participants(participants) subject = validated_data.get('subject', None) if not subject and isinstance(participants, list) and len(participants) == 1: # Create or get a direct channel # if only one other participant is given and no subject is stated for the communication current_user = self.get_current_user() channel = get_or_create_direct_channel(current_user, participants[0]) else: if not subject: raise ValidationError( {'subject': 'Enter a subject for this conversation'}) channel = Channel.objects.create(**validated_data) self.save_participants(channel, participants) return channel def update(self, instance, validated_data): participants = None if 'participants' in validated_data: participants = validated_data.pop('participants') participants = self.clean_participants(participants) for attr, value in validated_data.items(): setattr(instance, attr, value) subject = validated_data.get('subject', None) if not subject and isinstance(participants, list) and len(participants) > 1: raise ValidationError( {'subject': 'Enter a subject for this conversation'}) instance.save() self.save_participants(instance, participants) return instance def clean_participants(self, participants): current_user = self.get_current_user() if isinstance(participants, (list, tuple)) and current_user: return [ user_id for user_id in participants if user_id != current_user.id ] return participants def save_participants(self, instance, participants): if participants: participants.append(instance.created_by) for user in participants: try: ChannelUser.objects.update_or_create(channel=instance, user=user) except: pass def get_user(self, obj): current_user = self.get_current_user() if current_user: receiver = obj.get_receiver(current_user) if receiver: return SimpleUserSerializer(receiver).data return None def get_new(self, obj): user = self.get_current_user() if user: return channel_activity_new_messages_filter( queryset=obj.target_actions.filter( channels__channeluser__user=user), user=user).count() return 0 def get_last_read(self, obj): user = self.get_current_user() if user: try: return obj.channeluser_set.get(user=user).last_read except: pass else: return obj.last_read return 0
class ProfileSerializer(DetailAnnotatedModelSerializer): user = serializers.PrimaryKeyRelatedField( required=False, read_only=True, default=CreateOnlyCurrentUserDefault()) first_name = serializers.CharField(required=False, write_only=True, max_length=20) last_name = serializers.CharField(required=False, write_only=True, max_length=20) city = serializers.CharField(required=False, allow_blank=True, allow_null=True) skills = serializers.CharField(required=False, allow_blank=True, allow_null=True) skills_details = SkillsDetailsSerializer(required=False, allow_null=True) skill_categories = serializers.JSONField(required=False, write_only=True) country = CountryField(required=False) class Meta: model = UserProfile exclude = () details_serializer = ProfileDetailsSerializer def validate(self, attrs): payment_method = attrs.get('payment_method', None) if payment_method == PAYMENT_METHOD_MOBILE_MONEY: mobile_money_cc = attrs.get('mobile_money_cc', None) mobile_money_number = attrs.get('mobile_money_number', None) if not mobile_money_cc: raise ValidationError({ 'mobile_money_cc': 'Enter the country code for your mobile number' }) if not mobile_money_number: raise ValidationError( {'mobile_money_number': 'Enter your mobile money number'}) elif payment_method == PAYMENT_METHOD_BTC_ADDRESS: if not attrs.get('btc_address', None): raise ValidationError( {'btc_address': 'Enter a bitcoin address'}) return attrs def save_profile(self, validated_data, instance=None): user_data = self.get_user_data(validated_data) skills = None city = None skill_categories = None if 'skills' in validated_data: skills = validated_data.pop('skills') if 'city' in validated_data: city = validated_data.pop('city') if 'skill_categories' in validated_data: skill_categories = validated_data.pop('skill_categories') if instance: instance = super(ProfileSerializer, self).update(instance, validated_data) else: instance = super(ProfileSerializer, self).create(validated_data) self.save_user_info(instance, user_data) self.save_skills(instance, skills) self.save_city(instance, city) self.save_skill_categories(skill_categories) return instance def create(self, validated_data): return self.save_profile(validated_data) def update(self, instance, validated_data): return self.save_profile(validated_data, instance) def get_user_data(self, validated_data): user_data = dict() for user_key in ['first_name', 'last_name']: if user_key in validated_data: user_data[user_key] = validated_data.pop(user_key) return user_data def save_user_info(self, instance, user_data): user = instance.user if user: first_name = user_data.get('first_name') last_name = user_data.get('last_name') if first_name or last_name: user.first_name = first_name or user.first_name user.last_name = last_name or user.last_name user.save() def save_skills(self, profile, skills): if skills is not None: profile.skills = skills profile.save() def save_city(self, profile, city): if city: profile.city = city profile.save() def save_skill_categories(self, skill_categories): if skill_categories is not None: print('skill_categories', skill_categories) for category in skill_categories: print('category', category, skill_categories[category]) if category is not SKILL_TYPE_OTHER: for skill in skill_categories[category]: try: print( Skill.objects.filter( name=skill, type=SKILL_TYPE_OTHER).update( type=category)) except: pass
class SimpleProjectMetaSerializer(SimpleModelSerializer): created_by = SimplestUserSerializer(required=False, read_only=True, default=CreateOnlyCurrentUserDefault()) class Meta: model = ProjectMeta exclude = ('project',)
class AbstractEstimateSerializer(ContentTypeAnnotatedModelSerializer, DetailAnnotatedModelSerializer, GetCurrentUserAnnotatedSerializerMixin): user = SimpleUserSerializer(required=False, read_only=True, default=CreateOnlyCurrentUserDefault()) moderated_by = SimpleUserSerializer(required=False, read_only=True) activities = NestedWorkActivitySerializer(required=True, read_only=False, many=True) class Meta: model = AbstractEstimate fields = '__all__' read_only_fields = ('submitted_at', 'moderated_at', 'reviewed_at', 'created_at', 'updated_at') details_serializer = AbstractEstimateDetailsSerializer def validate_activities(self, value): if not value: raise ValidationError('This field is required') return value def pop_related_objects(self, validated_data, instance=None): self.activities = None if 'activities' in validated_data: self.activities = validated_data.pop('activities') def save_related_objects(self, instance): self.save_activities(instance, self.activities) def on_create_complete(self, instance): pass def on_status_change(self, instance): pass def save_estimate(self, validated_data, instance=None): self.pop_related_objects(validated_data, instance) initial_status = STATUS_INITIAL is_update = bool(instance) if is_update: initial_status = instance.status # Clone new estimate if previous was declined or rejected and reset useful defaults if instance.status in [STATUS_DECLINED, STATUS_REJECTED]: instance.pk = None instance.status = STATUS_INITIAL instance.moderated_by = None instance.moderated_at = None instance.reviewed_by = None instance.moderated_at = None instance.save() if initial_status != validated_data.get('status'): if validated_data.get('status') == STATUS_SUBMITTED: validated_data['submitted_at'] = datetime.datetime.utcnow() if validated_data.get('status') in [ STATUS_APPROVED, STATUS_DECLINED ]: validated_data['moderated_by'] = self.get_current_user( ) or None validated_data['moderated_at'] = datetime.datetime.utcnow() if validated_data.get('status') in [ STATUS_ACCEPTED, STATUS_REJECTED ]: validated_data['reviewed_by'] = self.get_current_user( ) or None validated_data['reviewed_at'] = datetime.datetime.utcnow() instance = super(AbstractEstimateSerializer, self).update(instance, validated_data) else: instance = super(AbstractEstimateSerializer, self).create(validated_data) self.save_related_objects(instance) if is_update: if initial_status != instance.status: self.on_status_change(instance) else: # Triggered here instead of in the post_save signal to allow related objects to be attached first self.on_create_complete(instance) return instance def create(self, validated_data): return self.save_estimate(validated_data) def update(self, instance, validated_data): return self.save_estimate(validated_data, instance=instance) def save_activities(self, instance, activities): if activities: c_type = ContentType.objects.get_for_model(self.Meta.model) # Delete existing WorkActivity.objects.filter(content_type=c_type, object_id=instance.id).delete() for item in activities: try: item['content_type'] = c_type item['object_id'] = instance.id item['user'] = self.get_current_user() WorkActivity.objects.create(**item) except: pass
class TaskSerializer(ContentTypeAnnotatedModelSerializer, DetailAnnotatedModelSerializer, GetCurrentUserAnnotatedSerializerMixin): user = SimpleUserSerializer(required=False, read_only=True, default=CreateOnlyCurrentUserDefault()) pay = serializers.DecimalField(max_digits=19, decimal_places=4, required=False, read_only=True) display_fee = serializers.SerializerMethodField(required=False, read_only=True) amount = serializers.JSONField(required=False, read_only=True) is_payable = serializers.BooleanField(required=False, read_only=True) is_project = serializers.BooleanField(required=False, read_only=True) is_task = serializers.BooleanField(required=False, read_only=True) is_developer_ready = serializers.BooleanField(required=False, read_only=True) requires_estimate = serializers.BooleanField(required=False, read_only=True) excerpt = serializers.CharField(required=False, read_only=True) skills = serializers.CharField( required=False, error_messages={ 'blank': 'Please specify the skills required for this task' }) payment_status = serializers.CharField(required=False, read_only=True) deadline = serializers.DateTimeField(required=False, allow_null=True) can_apply = serializers.SerializerMethodField(read_only=True, required=False) can_claim = serializers.SerializerMethodField(read_only=True, required=False) can_return = serializers.SerializerMethodField(read_only=True, required=False) can_save = serializers.SerializerMethodField(read_only=True, required=False) is_participant = serializers.SerializerMethodField(read_only=True, required=False) is_admin = serializers.SerializerMethodField(read_only=True, required=False) my_participation = serializers.SerializerMethodField(read_only=True, required=False) summary = serializers.CharField(read_only=True, required=False) assignee = SimpleParticipationSerializer(required=False, read_only=True) participants = serializers.PrimaryKeyRelatedField( many=True, queryset=get_user_model().objects.all(), required=False, write_only=True) update_schedule_display = serializers.CharField(required=False, read_only=True) participation = NestedTaskParticipationSerializer(required=False, read_only=False, many=True) milestones = NestedProgressEventSerializer(required=False, read_only=False, many=True) progress_events = NestedProgressEventSerializer(required=False, read_only=True, many=True) ratings = SimpleRatingSerializer(required=False, read_only=False, many=True) uploads = UploadSerializer(required=False, read_only=True, many=True) all_uploads = UploadSerializer(required=False, read_only=True, many=True) invoice = TaskInvoiceSerializer(required=False, read_only=True) estimate = SimpleEstimateSerializer(required=False, read_only=True) quote = SimpleQuoteSerializer(required=False, read_only=True) pm = SimpleUserSerializer(required=False, read_only=True) class Meta: model = Task exclude = ('applicants', ) read_only_fields = ('created_at', 'paid', 'paid_at', 'invoice_date', 'btc_address', 'btc_price', 'pay_distributed') extra_kwargs = { 'type': { 'required': True, 'allow_blank': False, 'allow_null': False }, 'scope': { 'required': True, 'allow_blank': False, 'allow_null': False } } details_serializer = TaskDetailsSerializer def validate(self, attrs): has_parent = attrs.get('parent', None) or (self.instance and self.instance.parent) scope = attrs.get('scope', None) or (self.instance and self.instance.scope) is_project = attrs.get( 'is_project', None) or (self.instance and self.instance.is_project) has_requirements = attrs.get( 'is_project', None) or (self.instance and self.instance.has_requirements) coders_needed = attrs.get('coders_needed', None) pm_required = attrs.get('pm_required', None) fee = attrs.get('fee', None) title = attrs.get('title', None) skills = attrs.get('skills', None) visibility = attrs.get('visibility', None) description = attrs.get('description', None) email = self.initial_data.get('email', None) first_name = self.initial_data.get('first_name', None) last_name = self.initial_data.get('last_name', None) current_user = self.get_current_user() errors = dict() if current_user and current_user.is_authenticated(): if scope == TASK_SCOPE_TASK or has_parent: if not has_parent and fee: MinValueValidator( 15, message='Minimum pledge amount is EUR 15')(fee) if not title and not self.partial: errors.update({'title': 'This field is required.'}) if not description and not self.partial: errors.update({'description': 'This field is required.'}) if not skills and not self.partial: errors.update({'skills': 'This field is required.'}) if visibility == VISIBILITY_CUSTOM and not ( attrs.get('participation', None) or attrs.get('participants', None)): errors.update({ 'visibility': 'Please choose at least one developer for this task' }) if scope == TASK_SCOPE_TASK and description and self.partial and len( description.split(' ')) <= 15: errors.update({ 'description': 'Please provide a more detailed description.' }) if scope == TASK_SCOPE_ONGOING: if not skills and not self.partial: errors.update({'skills': 'This field is required.'}) if not coders_needed and not self.partial: errors.update({'coders_needed': 'This field is required.'}) if scope == TASK_SCOPE_PROJECT: if not title and not self.partial: errors.update({'title': 'This field is required.'}) if not skills and not self.partial: errors.update({'skills': 'This field is required.'}) if not pm_required and not self.partial: errors.update({'pm_required': 'This field is required.'}) else: if not description: errors.update({'description': 'This field is required.'}) if email: try: get_user_model().objects.get(email=email) errors.update({ 'form': 'Looks like you already have a Tunga account. Please login to create new tasks.', 'email': 'This email address is already attached to an account on Tunga' }) except get_user_model().DoesNotExist: pass else: errors.update({'email': 'This field is required.'}) if not first_name: errors.update({'first_name': 'This field is required.'}) if not last_name: errors.update({'last_name': 'This field is required.'}) if errors: raise ValidationError(errors) return attrs def save_task(self, validated_data, instance=None): current_user = self.get_current_user() if current_user and current_user.is_authenticated( ) and not instance and not profile_check(current_user): ValidationError( 'You need complete your profile before you can post tasks') if instance and 'fee' in validated_data and validated_data[ 'fee'] < instance.fee: raise ValidationError({ 'fee': 'You cannot reduce the fee for the task, Please contact [email protected] for assistance' }) skills = None participation = None milestones = None participants = None ratings = None if 'skills' in validated_data: skills = validated_data.pop('skills') if 'participation' in validated_data: participation = validated_data.pop('participation') if 'milestones' in validated_data: milestones = validated_data.pop('milestones') if 'participants' in validated_data: participants = validated_data.pop('participants') if 'ratings' in validated_data: ratings = validated_data.pop('ratings') initial_apply = True initial_closed = False initial_approved = False new_user = None is_update = bool(instance) if instance: initial_apply = instance.apply initial_closed = instance.closed initial_approved = instance.approved if not instance.closed and validated_data.get('closed'): validated_data['closed_at'] = datetime.datetime.utcnow() if not instance.paid and validated_data.get('paid'): validated_data['paid_at'] = datetime.datetime.utcnow() instance = super(TaskSerializer, self).update(instance, validated_data) else: if not current_user or not current_user.is_authenticated(): validated_data['source'] = TASK_SOURCE_NEW_USER if participation or participants: # Close applications if paticipants are provided when creating task validated_data['apply'] = False validated_data['apply_closed_at'] = datetime.datetime.utcnow() if not current_user or not current_user.is_authenticated(): # Create user and add them as the creator or task, indicate if task was unauthenticated email = self.initial_data.get('email', None) first_name = self.initial_data.get('first_name', None) last_name = self.initial_data.get('last_name', None) new_user = get_user_model().objects.create_user( username=email, email=email, password=get_user_model().objects.make_random_password(), first_name=first_name, last_name=last_name, type=USER_TYPE_PROJECT_OWNER, source=USER_SOURCE_TASK_WIZARD) if new_user: validated_data.update({'user': new_user}) user_signed_up.send(sender=get_user_model(), request=None, user=new_user) instance = super(TaskSerializer, self).create(validated_data) self.save_skills(instance, skills) self.save_participants(instance, participants) self.save_participation(instance, participation) self.save_milestones(instance, milestones) self.save_ratings(instance, ratings) if is_update: if not initial_approved and instance.approved: task_approved.send(sender=Task, task=instance) if initial_apply and not instance.apply: task_applications_closed.send(sender=Task, task=instance) if not initial_closed and instance.closed: task_closed.send(sender=Task, task=instance) else: # Triggered here instead of in the post_save signal to allow skills to be attached first # TODO: Consider moving this trigger notify_new_task.delay(instance.id, new_user=bool(new_user)) return instance def create(self, validated_data): return self.save_task(validated_data) def update(self, instance, validated_data): return self.save_task(validated_data, instance=instance) def save_skills(self, task, skills): if skills is not None: task.skills = skills task.save() def save_participation(self, task, participation): if participation: new_assignee = None for item in participation: if 'accepted' in item and item.get('accepted', False): item['activated_at'] = datetime.datetime.utcnow() defaults = item if isinstance(defaults, dict): current_user = self.get_current_user() participation_creator = task.user if current_user and current_user.is_authenticated( ) and current_user != item.get('user', None): participation_creator = current_user defaults['created_by'] = participation_creator try: participation_obj, created = Participation.objects.update_or_create( task=task, user=item['user'], defaults=defaults) if (not created) and 'accepted' in item: participation_response.send( sender=Participation, participation=participation_obj) if 'assignee' in item and item['assignee']: new_assignee = item['user'] except: pass if new_assignee: Participation.objects.exclude(user=new_assignee).filter( task=task).update(assignee=False) def save_milestones(self, task, milestones): if milestones: for item in milestones: event_type = item.get('type', PROGRESS_EVENT_TYPE_MILESTONE) if event_type != PROGRESS_EVENT_TYPE_MILESTONE: continue defaults = {'created_by': self.get_current_user() or task.user} defaults.update(item) try: ProgressEvent.objects.update_or_create( task=task, type=event_type, due_at=item['due_at'], defaults=defaults) except: pass def save_ratings(self, task, ratings): if ratings: for item in ratings: try: Rating.objects.update_or_create( content_type=ContentType.objects.get_for_model(task), object_id=task.id, criteria=item['criteria'], defaults=item) except: pass def save_participants(self, task, participants): # TODO: Remove and move existing code to using save_participation if participants: assignee = self.initial_data.get('assignee', None) confirmed_participants = self.initial_data.get( 'confirmed_participants', None) rejected_participants = self.initial_data.get( 'rejected_participants', None) created_by = self.get_current_user() or task.user changed_assignee = False for user in participants: try: defaults = {'created_by': created_by} if assignee: defaults['assignee'] = bool(user.id == assignee) if rejected_participants and user.id in rejected_participants: defaults['accepted'] = False defaults['responded'] = True if confirmed_participants and user.id in confirmed_participants: defaults['accepted'] = True defaults['responded'] = True defaults['activated_at'] = datetime.datetime.utcnow() participation_obj, created = Participation.objects.update_or_create( task=task, user=user, defaults=defaults) if (not created) and (user.id in rejected_participants or user.id in confirmed_participants): participation_response.send( sender=Participation, participation=participation_obj) if user.id == assignee: changed_assignee = True except: pass if assignee and changed_assignee: Participation.objects.exclude(user__id=assignee).filter( task=task).update(assignee=False) def get_display_fee(self, obj): user = self.get_current_user() amount = None if not obj.pay: return None if user and user.is_developer: amount = obj.pay_dev * (1 - obj.tunga_ratio_dev) return obj.display_fee(amount=amount) def get_can_apply(self, obj): if obj.closed or not obj.apply or not obj.is_developer_ready: return False user = self.get_current_user() if user: if obj.user == user or not user.is_developer or user.pending or not profile_check( user): return False return obj.applicants.filter(id=user.id).count() == 0 and \ obj.participation_set.filter(user=user).count() == 0 return False def get_can_claim(self, obj): if obj.closed or obj.is_task: return False user = self.get_current_user() if user and user.is_authenticated() and ( user.is_project_manager or user.is_admin) and not obj.pm and ( obj.pm_required or obj.source == TASK_SOURCE_NEW_USER): return True return False def get_can_return(self, obj): if obj.closed or obj.estimate: return False user = self.get_current_user() if user and user.is_authenticated() and obj.pm == user: return True return False def get_can_save(self, obj): return False def get_is_participant(self, obj): user = self.get_current_user() if user: return obj.subtask_participants_inclusive_filter.filter( (Q(accepted=True) | Q(responded=False)), user=user).count() > 0 return False def get_is_admin(self, obj): user = self.get_current_user() return obj.has_admin_access(user) def get_my_participation(self, obj): user = self.get_current_user() if user: try: participation = obj.participation_set.get(user=user) return { 'id': participation.id, 'user': participation.user.id, 'assignee': participation.assignee, 'accepted': participation.accepted, 'responded': participation.responded } except: pass return None
class IntegrationSerializer(ContentTypeAnnotatedModelSerializer, GetCurrentUserAnnotatedSerializerMixin): created_by = SimpleUserSerializer(required=False, read_only=True, default=CreateOnlyCurrentUserDefault()) events = serializers.PrimaryKeyRelatedField( many=True, queryset=IntegrationEvent.objects.all(), required=False, read_only=False) meta = NestedIntegrationMetaSerializer(required=False, read_only=False, many=True, source='integrationmeta_set') # Write Only repo = serializers.JSONField(required=False, write_only=True, allow_null=True) issue = serializers.JSONField(required=False, write_only=True, allow_null=True) project = serializers.JSONField(required=False, write_only=True, allow_null=True) team = serializers.JSONField(required=False, write_only=True, allow_null=True) channel = serializers.JSONField(required=False, write_only=True, allow_null=True) # Read Only repo_id = serializers.CharField(required=False, read_only=True) issue_id = serializers.CharField(required=False, read_only=True) project_id = serializers.CharField(required=False, read_only=True) project_task_id = serializers.CharField(required=False, read_only=True) team_id = serializers.CharField(required=False, read_only=True) team_name = serializers.CharField(required=False, read_only=True) channel_id = serializers.CharField(required=False, read_only=True) channel_name = serializers.CharField(required=False, read_only=True) class Meta: model = Integration exclude = ('secret', ) read_only_fields = ('created_at', 'updated_at') def send_creation_signal(self, instance): task_integration.send(sender=Integration, integration=instance) def save_integration(self, validated_data, instance=None): events = None if 'events' in validated_data: events = validated_data.pop('events') meta = None if 'meta' in validated_data: meta = validated_data.pop('meta') metadata_objects = dict() metadata_keys = ['repo', 'issue', 'project', 'team', 'channel'] for key in metadata_keys: if key in validated_data: metadata_objects[key] = validated_data.pop(key) if instance: instance = super(IntegrationSerializer, self).update(instance, validated_data) else: instance = super(IntegrationSerializer, self).create(validated_data) self.save_events(instance, events) self.save_meta(instance, meta) for key in metadata_keys: self.save_meta_object(instance, metadata_objects.get(key, None), key) self.send_creation_signal(instance) return instance def create(self, validated_data): return self.save_integration(validated_data) def update(self, instance, validated_data): return self.save_integration(validated_data, instance=instance) def save_events(self, instance, events): if events: instance.events.clear() for item in events: try: instance.events.add(item) except: pass def save_meta(self, instance, meta): if meta: for item in meta: defaults = { 'created_by': self.get_current_user() or instance.user } defaults.update(item) try: IntegrationMeta.objects.update_or_create( integration=instance, meta_key=item['meta_key'], defaults=defaults) except: pass def save_meta_object(self, instance, meta_object, prefix): if meta_object: for key in meta_object: defaults = { 'created_by': self.get_current_user() or instance.user, 'meta_key': '%s_%s' % (prefix, key), 'meta_value': clean_meta_value(meta_object[key]) } try: IntegrationMeta.objects.update_or_create( integration=instance, meta_key=defaults['meta_key'], defaults=defaults) except: pass
class MessageSerializer(DetailAnnotatedSerializer): user = serializers.PrimaryKeyRelatedField(required=False, read_only=True, default=CreateOnlyCurrentUserDefault()) recipients = serializers.PrimaryKeyRelatedField(many=True, queryset=get_user_model().objects.all(), allow_null=True, allow_empty=True) excerpt = serializers.CharField(required=False, read_only=True) is_read = serializers.SerializerMethodField(read_only=True, required=False) attachments = serializers.SerializerMethodField(read_only=True, required=False) class Meta: model = Message read_only_fields = ('created_at',) details_serializer = MessageDetailsSerializer def create(self, validated_data): to_users = None to_users = validated_data.pop('recipients') message = Message.objects.create(**validated_data) self.save_recipents(message, to_users) return message def update(self, instance, validated_data): to_users = None if 'recipients' in validated_data: to_users = validated_data.pop('recipients') for attr, value in validated_data.items(): setattr(instance, attr, value) instance.save() self.save_recipents(instance, to_users) return instance def save_recipents(self, message, to_users): if to_users: for user in to_users: try: Reception.objects.update_or_create(message=message, user=user) except: pass def get_is_read(self, obj): request = self.context.get("request", None) if request: user = getattr(request, "user", None) if user: if obj.user == user: replies = obj.replies.exclude(user=user) if obj.read_at: return replies.filter(created_at__gt=obj.read_at).count() == 0 return replies.count() == 0 return obj.reception_set.filter( user=user, read_at__isnull=False ).annotate( latest_created_at=Max( Case( When( ~Q(message__replies__user=request.user), then='message__replies__created_at' ), default=Value(obj.created_at), output_field=DateTimeField() ) ) ).filter(Q(latest_created_at__isnull=True) | Q(read_at__gt=F('latest_created_at')), read_at__gt=obj.created_at).count() >= 1 return False def get_attachments(self, obj): content_type = ContentType.objects.get_for_model(Message) attachments = Attachment.objects.filter(content_type=content_type, object_id=obj.id) return SimpleAttachmentSerializer(attachments, many=True).data
class ReplySerializer(DetailAnnotatedSerializer): user = serializers.PrimaryKeyRelatedField(required=False, read_only=True, default=CreateOnlyCurrentUserDefault()) excerpt = serializers.CharField(required=False, read_only=True) attachments = serializers.SerializerMethodField(read_only=True, required=False) class Meta: model = Reply read_only_fields = ('created_at',) details_serializer = ReplyDetailsSerializer def get_attachments(self, obj): content_type = ContentType.objects.get_for_model(Reply) attachments = Attachment.objects.filter(content_type=content_type, object_id=obj.id) return SimpleAttachmentSerializer(attachments, many=True).data