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
示例#3
0
class SocialPlatformSerializer(serializers.ModelSerializer):
    created_by = serializers.HiddenField(required=False, default=CreateOnlyCurrentUserDefault())

    class Meta:
        model = SocialPlatform
        exclude = ('created_at',)
示例#4
0
class SocialLinkSerializer(DetailAnnotatedModelSerializer):
    user = serializers.PrimaryKeyRelatedField(required=False, read_only=True, default=CreateOnlyCurrentUserDefault())

    class Meta:
        model = SocialLink
        exclude = ('created_at',)
        details_serializer = SocialLinkDetailsSerializer
示例#5
0
class ConnectionSerializer(DetailAnnotatedModelSerializer):
    from_user = serializers.PrimaryKeyRelatedField(required=False, read_only=True, default=CreateOnlyCurrentUserDefault())

    class Meta:
        model = Connection
        exclude = ('created_at',)
        details_serializer = ConnectionDetailsSerializer
示例#6
0
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()
示例#7
0
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
示例#8
0
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
示例#9
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
示例#10
0
class SimpleProjectMetaSerializer(SimpleModelSerializer):
    created_by = SimplestUserSerializer(required=False, read_only=True, default=CreateOnlyCurrentUserDefault())

    class Meta:
        model = ProjectMeta
        exclude = ('project',)
示例#11
0
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
示例#12
0
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
示例#13
0
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
示例#14
0
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
示例#15
0
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