예제 #1
0
 def create(self, request, *args, **kwargs):
     serializer = BatchFileSerializer(data=request.data)
     if serializer.is_valid():
         batch_file = serializer.create()
         serializer = BatchFileSerializer(instance=batch_file)
         return Response(data=serializer.data, status=status.HTTP_201_CREATED)
     else:
         raise serializers.ValidationError(detail=serializer.errors)
예제 #2
0
 def create(self, request, *args, **kwargs):
     serializer = BatchFileSerializer(data=request.data)
     if serializer.is_valid():
         batch_file = serializer.create()
         serializer = BatchFileSerializer(instance=batch_file)
         return Response(data=serializer.data, status=status.HTTP_201_CREATED)
     else:
         return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
예제 #3
0
 def create(self, request, *args, **kwargs):
     serializer = BatchFileSerializer(data=request.data)
     if serializer.is_valid():
         batch_file = serializer.create()
         serializer = BatchFileSerializer(instance=batch_file)
         return Response(data=serializer.data, status=status.HTTP_201_CREATED)
     else:
         raise serializers.ValidationError(detail=serializer.errors)
예제 #4
0
 def create(self, request, *args, **kwargs):
     serializer = BatchFileSerializer(data=request.data)
     if serializer.is_valid():
         batch_file = serializer.create()
         serializer = BatchFileSerializer(instance=batch_file)
         return Response(data=serializer.data,
                         status=status.HTTP_201_CREATED)
     else:
         return Response(serializer.errors,
                         status=status.HTTP_400_BAD_REQUEST)
예제 #5
0
파일: project.py 프로젝트: 9vinny/hcc-daemo
class ProjectSerializer(DynamicFieldsModelSerializer):
    total_tasks = serializers.SerializerMethodField()
    age = serializers.SerializerMethodField()
    has_comments = serializers.SerializerMethodField()
    available_tasks = serializers.IntegerField(read_only=True)
    in_progress = serializers.IntegerField(read_only=True)
    completed = serializers.IntegerField(read_only=True)
    paid_count = serializers.IntegerField(read_only=True)
    awaiting_review = serializers.IntegerField(read_only=True)
    checked_out = serializers.IntegerField(read_only=True)
    returned = serializers.IntegerField(read_only=True)
    comments = serializers.SerializerMethodField()
    relaunch = serializers.SerializerMethodField()

    requester_rating = serializers.FloatField(read_only=True, required=False)
    raw_rating = serializers.IntegerField(read_only=True, required=False)

    # owner = UserSerializer(fields=('username', 'id'), read_only=True)
    requester_handle = serializers.CharField(read_only=True)
    batch_files = BatchFileSerializer(
        many=True,
        read_only=True,
        fields=('id', 'name', 'size', 'column_headers', 'format',
                'number_of_rows', 'first_row', 'file'))
    template = TemplateSerializer(many=False, required=False)

    name = serializers.CharField(default='Untitled Project')
    status = serializers.IntegerField(default=models.Project.STATUS_DRAFT)
    file_id = serializers.IntegerField(write_only=True,
                                       allow_null=True,
                                       required=False)
    num_rows = serializers.IntegerField(write_only=True,
                                        allow_null=True,
                                        required=False)
    deadline = serializers.DateTimeField(required=False)
    revisions = serializers.SerializerMethodField()
    hash_id = serializers.SerializerMethodField()
    review_price = serializers.FloatField(required=False)
    amount_paid = serializers.FloatField(required=False)
    expected_payout_amount = serializers.FloatField(required=False)
    has_review = serializers.BooleanField(required=False)
    payout_available_by = serializers.DateTimeField(required=False)
    last_submitted_at = serializers.DateTimeField(required=False)
    template_id = serializers.IntegerField(required=False, allow_null=True)

    class Meta:
        model = models.Project
        fields = ('id', 'name', 'description', 'status', 'repetition',
                  'deadline', 'timeout', 'template', 'batch_files',
                  'deleted_at', 'created_at', 'updated_at', 'price',
                  'has_data_set', 'data_set_location', 'total_tasks',
                  'file_id', 'age', 'is_micro', 'is_prototype', 'has_review',
                  'task_time', 'allow_feedback', 'feedback_permissions',
                  'min_rating', 'has_comments', 'available_tasks', 'comments',
                  'num_rows', 'requester_rating', 'raw_rating', 'post_mturk',
                  'qualification', 'relaunch', 'group_id', 'revisions',
                  'hash_id', 'is_api_only', 'in_progress', 'awaiting_review',
                  'completed', 'review_price', 'returned', 'requester_handle',
                  'allow_price_per_task', 'task_price_field',
                  'discussion_link', 'aux_attributes', 'payout_available_by',
                  'paid_count', 'expected_payout_amount', 'amount_paid',
                  'template_id', 'checked_out', 'publish_at', 'published_at',
                  'template_id', 'enable_boomerang', 'last_submitted_at')
        read_only_fields = ('created_at', 'updated_at', 'deleted_at',
                            'has_comments', 'available_tasks', 'comments',
                            'template', 'is_api_only', 'discussion_link',
                            'aux_attributes', 'payout_available_by',
                            'paid_count', 'expected_payout_amount',
                            'amount_paid', 'published_at', 'last_submitted_at')

        validators = [ProjectValidator()]

    def to_representation(self, instance):
        data = super(ProjectSerializer, self).to_representation(instance)
        task_time = int(instance.task_time.total_seconds() /
                        60) if instance.task_time is not None else None
        timeout = int(instance.timeout.total_seconds() /
                      60) if instance.timeout is not None else None
        review_project = models.Project.objects.filter(
            parent_id=instance.group_id,
            is_review=True,
            deleted_at__isnull=True).first()
        if review_project is not None:
            review_price = review_project.price
            data.update({'review_price': review_price})
        # data.update({'has_review': review_project is not None})
        data.update({'task_time': task_time, 'timeout': timeout})
        data.update({'price': instance.price})
        return data

    def to_internal_value(self, data):
        if 'task_time' in data and data['task_time'] is not None:
            data['task_time'] = "00:{}:00".format(data['task_time'])
        if 'timeout' in data and data['timeout'] is not None:
            data['timeout'] = "00:{}:00".format(data['timeout'])
        return super(ProjectSerializer, self).to_internal_value(data)

    def create(self, with_defaults=True, **kwargs):
        template_initial = self.validated_data.pop(
            'template') if 'template' in self.validated_data else None
        template_items = template_initial['items'] if template_initial else []

        template = {
            "name":
            template_initial.get('name', generate_random_id())
            if template_initial is not None else 't_' + generate_random_id(),
            "items":
            template_items
        }
        if 'post_mturk' in self.validated_data:
            self.validated_data.pop('post_mturk')
        template_serializer = TemplateSerializer(data=template)
        project = models.Project.objects.create(owner=kwargs['owner'],
                                                amount_due=0,
                                                post_mturk=False,
                                                **self.validated_data)
        if template_serializer.is_valid():
            project_template = template_serializer.create(
                with_defaults=with_defaults,
                is_review=False,
                owner=kwargs['owner'])
            project.template = project_template
        else:
            raise ValidationError(template_serializer.errors)

        project.group_id = project.id

        # if not with_defaults:
        #     project.status = models.Project.STATUS_IN_PROGRESS
        #     project.published_at = timezone.now()
        # self.instance = project
        # if not project.is_paid:
        #     self.pay(self.instance.price * self.instance.repetition)
        if with_defaults:
            self.create_task(project.id)
        project.save()
        # self.create_review(project=project, template_data=template)
        models.BoomerangLog.objects.create(
            object_id=project.group_id,
            min_rating=project.min_rating,
            rating_updated_at=project.rating_updated_at,
            reason='DEFAULT')

        return project

    @staticmethod
    def create_review(project, template_data, parent_review_project=None):
        project_name = 'Peer Review for ' + project.name

        review_project = models.Project.objects.create(
            name=project_name,
            owner=project.owner,
            parent=project,
            is_prototype=False,
            min_rating=1.99,
            post_mturk=False,
            timeout=project.timeout,
            is_review=True,
            deleted_at=timezone.now())
        if parent_review_project is not None:
            review_project.price = parent_review_project.price
        template_serializer = TemplateSerializer(data=template_data)
        if template_serializer.is_valid():
            review_template = template_serializer.create(with_defaults=False,
                                                         is_review=True,
                                                         owner=project.owner)
            review_project.template = review_template
        else:
            raise ValidationError(template_serializer.errors)
        review_project.group_id = review_project.id
        review_project.save()
        return review_project

    def update(self, *args, **kwargs):
        self.instance.name = self.validated_data.get('name',
                                                     self.instance.name)
        self.instance.price = self.validated_data.get('price',
                                                      self.instance.price)

        # review_project = models.Project.objects.filter(parent_id=self.instance.group_id, is_review=True).first()
        # has_review = self.validated_data.get('has_review', review_project.deleted_at is None)
        self.instance.timeout = self.validated_data.get(
            'timeout', self.instance.timeout)
        # if review_project is not None:
        #     review_project.price = self.validated_data.get('review_price', review_project.price)
        #     review_project.timeout = self.instance.timeout
        #     review_project.deleted_at = None if has_review else timezone.now()
        #     review_project.save()

        self.instance.repetition = self.validated_data.get(
            'repetition', self.instance.repetition)
        self.instance.deadline = self.validated_data.get(
            'deadline', self.instance.deadline)
        self.instance.publish_at = self.validated_data.get(
            'publish_at', self.instance.publish_at)

        # self.instance.post_mturk = self.validated_data.get('post_mturk', self.instance.post_mturk)
        self.instance.qualification = self.validated_data.get(
            'qualification', self.instance.qualification)
        self.instance.allow_price_per_task = self.validated_data.get(
            'allow_price_per_task', self.instance.allow_price_per_task)
        self.instance.task_price_field = self.validated_data.get(
            'task_price_field', self.instance.task_price_field)
        self.instance.enable_boomerang = self.validated_data.get(
            'enable_boomerang', self.instance.enable_boomerang)
        self.instance.template_id = self.validated_data.get(
            'template_id', self.instance.template_id)

        self.instance.save()
        return self.instance

    def update_status(self, *args, **kwargs):
        status = self.initial_data.get('status', self.instance.status)
        validator = ProjectValidator()
        validator.set_context(self)
        validator.__call__(value={'status': status})
        self.instance.status = status
        # mturk_update_status.delay({'id': self.instance.id, 'status': status})
        self.instance.save()
        return self.instance

    @staticmethod
    def get_age(model):
        from crowdsourcing.utils import get_relative_time

        if model.status == models.Project.STATUS_DRAFT:
            return get_relative_time(model.updated_at)
        else:
            return get_relative_time(model.published_at)

    @staticmethod
    def get_total_tasks(obj):
        return obj.tasks.all().count()

    @staticmethod
    def get_has_comments(obj):
        return obj.comments.count() > 0

    @staticmethod
    def get_comments(obj):
        if obj:
            comments = []
            tasks = obj.tasks.all()
            for task in tasks:
                task_comments = task.comments.all()
                for task_comment in task_comments:
                    comments.append(task_comment)
            serializer = TaskCommentSerializer(many=True,
                                               instance=comments,
                                               read_only=True)
            return serializer.data
        return []

    @staticmethod
    def has_csv_linkage(items):
        if items.count() > 0:
            template_items = items.all()
            for item in template_items:
                attribs = item.aux_attributes
                if 'question' in attribs and 'data_source' in attribs['question'] and \
                        attribs['question']['data_source'] is not None:
                    return True

                if 'options' in attribs and attribs['options'] is not None:
                    for option in attribs['options']:
                        if 'data_source' in option and option[
                                'data_source'] is not None:
                            return True
        return False

    @staticmethod
    def create_task(project_id):
        task_data = {"project": project_id, "data": {}}

        task_serializer = TaskSerializer(data=task_data)

        if task_serializer.is_valid():
            task_serializer.create()
        else:
            raise ValidationError(task_serializer.errors)

    def fork(self, *args, **kwargs):
        template = self.instance.template
        template_items = copy.copy(template.items.all())
        # batch_files = self.instance.batch_files.all()

        project = self.instance
        project.name += ' (copy)'
        project.status = models.Project.STATUS_DRAFT
        project.is_prototype = False
        project.parent_id = self.instance.id
        project.last_opened_at = None
        project.aux_attributes = {"sort_results_by": "worker_id"}
        project.amount_due = 0
        project.min_rating = 3.0
        project.is_paid = False
        project.previous_min_rating = 3.0
        project.discussion_link = None
        project.publish_at = None

        template.pk = None
        template.save()
        project.template = template

        review_project = models.Project.objects.filter(
            parent_id=self.instance.group_id, is_review=True).first()

        for template_item in template_items:
            template_item.pk = None
            template_item.template = template
            template_item.save()
        TemplateItemSerializer.rebuild_tree(template)
        project.id = None
        project.save()
        project.group_id = project.id
        project.save()

        template = {"name": 't_' + generate_random_id(), "items": []}

        self.create_task(project.id)
        self.create_review(project=project,
                           template_data=template,
                           parent_review_project=review_project)

        return project

    @staticmethod
    def create_revision(instance):
        models.Project.objects.filter(group_id=instance.group_id).update(
            status=models.Project.STATUS_PAUSED)
        template = TemplateSerializer.create_revision(
            instance=instance.template)
        batch_files = copy.copy(instance.batch_files.all())
        tasks = copy.copy(instance.tasks.all())
        # mturk_update_status.delay({'id': instance.id, 'status': models.Project.STATUS_PAUSED})
        instance.pk = None
        instance.template = template
        instance.status = models.Project.STATUS_DRAFT
        # instance.is_prototype = False
        instance.is_paid = False
        instance.save()
        for f in batch_files:
            models.ProjectBatchFile.objects.create(project=instance,
                                                   batch_file=f)

        for t in tasks:
            t.pk = None
            t.project = instance
        TaskSerializer.bulk_create(data=tasks)

        return instance

    def publish(self, amount_due):
        self.instance.repetition = self.validated_data.get(
            'repetition', self.instance.repetition)
        self.instance.published_at = timezone.now()

        review_project = models.Project.objects.filter(
            parent_id=self.instance.group_id,
            is_review=True,
            deleted_at__isnull=True).first()

        if review_project is not None and review_project.price is not None:
            review_project.status = models.Project.STATUS_IN_PROGRESS
            review_project.name = 'Peer Review for ' + self.instance.name
            review_project.published_at = timezone.now()
            review_project.save()

        status = models.Project.STATUS_IN_PROGRESS

        # if status != self.instance.status \
        #     and status in (models.Project.STATUS_PAUSED, models.Project.STATUS_IN_PROGRESS) and \
        #         self.instance.status in (models.Project.STATUS_PAUSED, models.Project.STATUS_IN_PROGRESS):
        #     # mturk_update_status.delay({'id': self.instance.id, 'status': status})
        self.instance.status = status
        self.instance.revised_at = timezone.now()
        if status == models.Project.STATUS_IN_PROGRESS and not self.instance.is_paid:
            self.pay(amount_due)
        self.instance.save()

    @staticmethod
    def get_relaunch(obj):
        """
            Not used since we removed csv
        Args:
            obj: project instance

        Returns:

        """
        previous_revision = models.Project.objects.prefetch_related('batch_files').filter(~Q(id=obj.id),
                                                                                          group_id=obj.group_id) \
            .order_by('-id').first()
        previous_batch_file = previous_revision.batch_files.first(
        ) if previous_revision else None
        batch_file = obj.batch_files.first()
        active_workers = models.TaskWorker.objects.active().filter(
            task__project__group_id=obj.group_id,
            task__exclude_at__isnull=True,
            status__in=[
                models.TaskWorker.STATUS_IN_PROGRESS,
                models.TaskWorker.STATUS_SUBMITTED,
                models.TaskWorker.STATUS_RETURNED,
                models.TaskWorker.STATUS_ACCEPTED
            ]).count()
        same_file = (previous_batch_file is not None and batch_file is not None
                     and previous_batch_file.id == batch_file.id)
        different_file = (previous_batch_file is not None and batch_file is None) or \
                         (previous_batch_file is None and batch_file is not None)
        if previous_revision is None or active_workers == 0:
            return {
                "is_forced": False,
                "ask_for_relaunch": False,
                "overlaps": False
            }
        elif (previous_batch_file is None and batch_file is None) or same_file:
            return {
                "is_forced": False,
                "ask_for_relaunch": True,
                "overlaps": True
            }
        elif different_file:
            return {
                "is_forced": True,
                "ask_for_relaunch": False,
                "overlaps": False
            }
        elif previous_batch_file.id != batch_file.id:
            return {
                "is_forced": False,
                "ask_for_relaunch": True,
                "overlaps": True
            }

    def pay(self, amount_due, *args, **kwargs):
        #Bir - Commenting out the stripe balance operation for user
        # requester_account = models.FinancialAccount.objects.get(owner_id=self.instance.owner_id,
        #                                                         type=models.FinancialAccount.TYPE_REQUESTER,
        #                                                         is_system=False).id
        # system_account = models.FinancialAccount.objects.get(is_system=True,
        #                                                      type=models.FinancialAccount.TYPE_ESCROW).id
        # transaction_data = {
        #     'sender': requester_account,
        #     'recipient': system_account,
        #     'amount': amount_due,
        #     'method': 'daemo',
        #     'sender_type': models.Transaction.TYPE_PROJECT_OWNER,
        #     'reference': 'P#' + str(self.instance.id)
        # }
        # if amount_due < 0:
        #     transaction_data['sender'] = system_account
        #     transaction_data['recipient'] = requester_account
        #     transaction_data['amount'] = abs(amount_due)
        #
        # transaction_serializer = TransactionSerializer(data=transaction_data)
        # if transaction_serializer.is_valid():
        #     if amount_due != 0:
        #         transaction_serializer.create()
        # self.instance.owner.stripe_customer.account_balance -= int(amount_due * 100)#Bir - Commenting out the stripe balance operation for user
        # self.instance.owner.stripe_customer.save()#Bir - Commenting out the stripe balance operation for user
        # self.instance.is_paid = True
        # self.instance.save()
        # else:
        #     raise ValidationError('Error in payment')
        self.instance.is_paid = True
        self.instance.save()

    @staticmethod
    def get_revisions(obj):
        return models.Project.objects.active().filter(
            group_id=obj.group_id).order_by('id').values_list('id', flat=True)

    def reset_boomerang(self):
        update_project_boomerang.delay(self.instance.id)

    @staticmethod
    def get_hash_id(obj):
        return to_hash(obj.group_id)

    @staticmethod
    def _set_aux_attributes(project, price_data):
        if project.aux_attributes is None:
            project.aux_attributes = {}
        if project.price is not None:
            if not len(price_data):
                max_price = float(project.price)
                min_price = float(project.price)
                median_price = float(project.price)
            else:
                max_price = float(np.max(price_data))
                min_price = float(np.min(price_data))
                median_price = float(np.median(price_data))
            project.aux_attributes.update({
                "min_price": min_price,
                "max_price": max_price,
                "median_price": median_price
            })
        project.save()

    def create_tasks(self, project_id, file_deleted):
        project = models.Project.objects.filter(pk=project_id).first()
        if project is None:
            return 'NOOP'
        previous_rev = models.Project.objects.prefetch_related('batch_files', 'tasks'). \
            filter(~Q(id=project.id), group_id=project.group_id).order_by('-id').first()

        previous_batch_file = previous_rev.batch_files.first(
        ) if previous_rev else None
        models.Task.objects.filter(project=project).delete()
        if file_deleted:
            models.Task.objects.filter(project=project).delete()
            task_data = {"project_id": project_id, "data": {}}
            task = models.Task.objects.create(**task_data)
            if previous_batch_file is None and previous_rev is not None:
                task.group_id = previous_rev.tasks.all().first().group_id
            else:
                task.group_id = task.id
            task.save()
            self._set_aux_attributes(project, [])
            project.batch_files.all().delete()
            return 'SUCCESS'
        try:
            with transaction.atomic():
                data = project.batch_files.first().parse_csv()
                task_obj = []
                x = 0
                previous_tasks = previous_rev.tasks.all().order_by(
                    'row_number') if previous_batch_file else []
                previous_count = len(previous_tasks)
                for row in data:
                    x += 1
                    hash_digest = hash_task(row)
                    price = None
                    if project.allow_price_per_task and project.task_price_field is not None:
                        price = row.get(project.task_price_field)
                        if not isinstance(price, (float, int, Decimal)):
                            price = None

                    t = models.Task(data=row,
                                    hash=hash_digest,
                                    project_id=int(project_id),
                                    row_number=x,
                                    price=price)
                    if previous_batch_file is not None and x <= previous_count:
                        if len(
                                set(row.items())
                                ^ set(previous_tasks[x -
                                                     1].data.items())) == 0:
                            t.group_id = previous_tasks[x - 1].group_id
                    task_obj.append(t)
                models.Task.objects.bulk_create(task_obj)
                price_data = models.Task.objects.filter(project_id=project_id, price__isnull=False). \
                    values_list('price', flat=True)
                self._set_aux_attributes(project, price_data)
                models.Task.objects.filter(project_id=project_id, group_id__isnull=True) \
                    .update(group_id=F('id'))
        except Exception as e:
            raise e
예제 #6
0
class ProjectSerializer(DynamicFieldsModelSerializer):
    total_tasks = serializers.SerializerMethodField()
    age = serializers.SerializerMethodField()
    has_comments = serializers.SerializerMethodField()
    available_tasks = serializers.IntegerField(read_only=True)
    in_progress = serializers.IntegerField(read_only=True)
    completed = serializers.IntegerField(read_only=True)
    awaiting_review = serializers.IntegerField(read_only=True)
    comments = serializers.SerializerMethodField()
    relaunch = serializers.SerializerMethodField()

    requester_rating = serializers.FloatField(read_only=True, required=False)
    raw_rating = serializers.IntegerField(read_only=True, required=False)

    owner = UserSerializer(fields=('username', 'id'), read_only=True)
    batch_files = BatchFileSerializer(many=True, read_only=True,
                                      fields=('id', 'name', 'size', 'column_headers', 'format', 'number_of_rows'))
    template = TemplateSerializer(many=False, required=False)

    name = serializers.CharField(default='Untitled Project')
    status = serializers.IntegerField(default=models.Project.STATUS_DRAFT)
    file_id = serializers.IntegerField(write_only=True, allow_null=True, required=False)
    num_rows = serializers.IntegerField(write_only=True, allow_null=True, required=False)
    deadline = serializers.DateTimeField(required=False)
    revisions = serializers.SerializerMethodField()
    hash_id = serializers.SerializerMethodField()
    review_price = serializers.FloatField(required=False)
    has_review = serializers.BooleanField(required=False)

    class Meta:
        model = models.Project
        fields = ('id', 'name', 'owner', 'description', 'status', 'repetition', 'deadline', 'timeout', 'template',
                  'batch_files', 'deleted_at', 'created_at', 'updated_at', 'price', 'has_data_set',
                  'data_set_location', 'total_tasks', 'file_id', 'age', 'is_micro', 'is_prototype', 'has_review',
                  'task_time', 'allow_feedback', 'feedback_permissions', 'min_rating', 'has_comments',
                  'available_tasks', 'comments', 'num_rows', 'requester_rating', 'raw_rating', 'post_mturk',
                  'qualification', 'relaunch', 'group_id', 'revisions', 'hash_id', 'is_api_only', 'in_progress',
                  'awaiting_review', 'completed', 'review_price')
        read_only_fields = (
            'created_at', 'updated_at', 'deleted_at', 'owner', 'has_comments', 'available_tasks',
            'comments', 'template', 'is_api_only')

        validators = [ProjectValidator()]

    def to_representation(self, instance):
        data = super(ProjectSerializer, self).to_representation(instance)
        task_time = int(instance.task_time.total_seconds() / 60) if instance.task_time is not None else None
        timeout = int(instance.timeout.total_seconds() / 60) if instance.timeout is not None else None
        review_project = models.Project.objects.filter(parent_id=instance.group_id, is_review=True,
                                                       deleted_at__isnull=True).first()
        if review_project is not None:
            review_price = review_project.price
            data.update({'review_price': review_price})
        data.update({'has_review': review_project is not None})
        data.update({'task_time': task_time, 'timeout': timeout})
        data.update({'price': instance.price})
        return data

    def to_internal_value(self, data):
        if 'task_time' in data and data['task_time'] is not None:
            data['task_time'] = "00:{}:00".format(data['task_time'])
        if 'timeout' in data and data['timeout'] is not None:
            data['timeout'] = "00:{}:00".format(data['timeout'])
        return super(ProjectSerializer, self).to_internal_value(data)

    def create(self, with_defaults=True, **kwargs):
        template_initial = self.validated_data.pop('template') if 'template' in self.validated_data else None
        template_items = template_initial['items'] if template_initial else []

        template = {
            "name": 't_' + generate_random_id(),
            "items": template_items
        }

        template_serializer = TemplateSerializer(data=template)
        self.validated_data.pop('post_mturk')
        project = models.Project.objects.create(owner=kwargs['owner'], post_mturk=True, amount_due=0,
                                                **self.validated_data)
        if template_serializer.is_valid():
            project_template = template_serializer.create(with_defaults=with_defaults, is_review=False,
                                                          owner=kwargs['owner'])
            project.template = project_template
        else:
            raise ValidationError(template_serializer.errors)

        project.group_id = project.id

        if not with_defaults:
            project.status = models.Project.STATUS_IN_PROGRESS
            project.published_at = timezone.now()
            # self.instance = project
            # if not project.is_paid:
            #     self.pay(self.instance.price * self.instance.repetition)
        # self.create_task(project.id)
        project.save()
        self.create_review(project=project, template_data=template)
        models.BoomerangLog.objects.create(object_id=project.group_id, min_rating=project.min_rating,
                                           rating_updated_at=project.rating_updated_at, reason='DEFAULT')

        return project

    @staticmethod
    def create_review(project, template_data, parent_review_project=None):
        project_name = 'Peer Review for ' + project.name

        review_project = models.Project.objects.create(name=project_name, owner=project.owner,
                                                       parent=project, is_prototype=False, min_rating=1.99,
                                                       post_mturk=True, timeout=project.timeout,
                                                       is_review=True, deleted_at=timezone.now())
        if parent_review_project is not None:
            review_project.price = parent_review_project.price
        template_serializer = TemplateSerializer(data=template_data)
        if template_serializer.is_valid():
            review_template = template_serializer.create(with_defaults=False, is_review=True,
                                                         owner=project.owner)
            review_project.template = review_template
        else:
            raise ValidationError(template_serializer.errors)
        review_project.group_id = review_project.id
        review_project.save()
        return review_project

    def update(self, *args, **kwargs):
        self.instance.name = self.validated_data.get('name', self.instance.name)
        self.instance.price = self.validated_data.get('price', self.instance.price)

        review_project = models.Project.objects.filter(parent_id=self.instance.group_id, is_review=True).first()
        has_review = self.validated_data.get('has_review', review_project.deleted_at is None)
        self.instance.timeout = self.validated_data.get('timeout', self.instance.timeout)
        if review_project is not None:
            review_project.price = self.validated_data.get('review_price', review_project.price)
            review_project.timeout = self.instance.timeout
        review_project.deleted_at = None if has_review else timezone.now()
        review_project.save()

        self.instance.repetition = self.validated_data.get('repetition', self.instance.repetition)
        self.instance.deadline = self.validated_data.get('deadline', self.instance.deadline)

        self.instance.post_mturk = self.validated_data.get('post_mturk', self.instance.post_mturk)
        self.instance.qualification = self.validated_data.get('qualification', self.instance.qualification)

        self.instance.save()
        return self.instance

    def update_status(self, *args, **kwargs):
        status = self.initial_data.get('status', self.instance.status)
        validator = ProjectValidator()
        validator.set_context(self)
        validator.__call__(value={'status': status})
        self.instance.status = status
        mturk_update_status.delay({'id': self.instance.id, 'status': status})
        self.instance.save()
        return self.instance

    @staticmethod
    def get_age(model):
        from crowdsourcing.utils import get_relative_time

        if model.status == models.Project.STATUS_DRAFT:
            return get_relative_time(model.updated_at)
        else:
            return get_relative_time(model.published_at)

    @staticmethod
    def get_total_tasks(obj):
        return obj.tasks.all().count()

    @staticmethod
    def get_has_comments(obj):
        return obj.comments.count() > 0

    @staticmethod
    def get_comments(obj):
        if obj:
            comments = []
            tasks = obj.tasks.all()
            for task in tasks:
                task_comments = task.comments.all()
                for task_comment in task_comments:
                    comments.append(task_comment)
            serializer = TaskCommentSerializer(many=True, instance=comments, read_only=True)
            return serializer.data
        return []

    @staticmethod
    def has_csv_linkage(items):
        if items.count() > 0:
            template_items = items.all()
            for item in template_items:
                attribs = item.aux_attributes
                if 'question' in attribs and 'data_source' in attribs['question'] and \
                        attribs['question']['data_source'] is not None:
                    return True

                if 'options' in attribs and attribs['options'] is not None:
                    for option in attribs['options']:
                        if 'data_source' in option and option['data_source'] is not None:
                            return True
        return False

    @staticmethod
    def create_task(project_id):
        task_data = {
            "project": project_id,
            "data": {}
        }

        task_serializer = TaskSerializer(data=task_data)

        if task_serializer.is_valid():
            task_serializer.create()
        else:
            raise ValidationError(task_serializer.errors)

    def fork(self, *args, **kwargs):
        template = self.instance.template
        template_items = copy.copy(template.items.all())
        # batch_files = self.instance.batch_files.all()

        project = self.instance
        project.name += ' (copy)'
        project.status = models.Project.STATUS_DRAFT
        project.is_prototype = False
        project.parent_id = self.instance.id
        template.pk = None
        template.save()
        project.template = template
        review_project = models.Project.objects.filter(parent_id=self.instance.group_id, is_review=True).first()

        for template_item in template_items:
            template_item.pk = None
            template_item.template = template
            template_item.save()
        project.id = None
        project.save()
        project.group_id = project.id
        project.save()

        template = {
            "name": 't_' + generate_random_id(),
            "items": []
        }
        self.create_review(project=project, template_data=template, parent_review_project=review_project)
        return project

    @staticmethod
    def create_revision(instance):
        models.Project.objects.filter(group_id=instance.group_id).update(status=models.Project.STATUS_PAUSED)
        template = TemplateSerializer.create_revision(instance=instance.template)
        # batch_files = copy.copy(instance.batch_files.all())
        tasks = copy.copy(instance.tasks.all())
        mturk_update_status.delay({'id': instance.id, 'status': models.Project.STATUS_PAUSED})
        instance.pk = None
        instance.template = template
        instance.status = models.Project.STATUS_DRAFT
        instance.is_prototype = False
        instance.is_paid = False
        instance.save()
        # for f in batch_files:
        #     models.ProjectBatchFile.objects.create(project=instance, batch_file=f)

        for t in tasks:
            t.pk = None
            t.project = instance
        TaskSerializer.bulk_create(data=tasks)

        return instance

    def publish(self, amount_due):
        self.instance.repetition = self.validated_data.get('repetition', self.instance.repetition)
        self.instance.published_at = timezone.now()

        review_project = models.Project.objects.filter(parent_id=self.instance.group_id, is_review=True,
                                                       deleted_at__isnull=True).first()

        if review_project is not None and review_project.price is not None:
            review_project.status = models.Project.STATUS_IN_PROGRESS
            review_project.name = 'Peer Review for ' + self.instance.name
            review_project.published_at = timezone.now()
            review_project.save()

        status = models.Project.STATUS_IN_PROGRESS

        if status != self.instance.status \
            and status in (models.Project.STATUS_PAUSED, models.Project.STATUS_IN_PROGRESS) and \
                self.instance.status in (models.Project.STATUS_PAUSED, models.Project.STATUS_IN_PROGRESS):
            mturk_update_status.delay({'id': self.instance.id, 'status': status})
        self.instance.status = status
        # TODO rm when mturk is removed if status == models.Project.STATUS_IN_PROGRESS and not self.instance.is_paid:
        #     self.pay(amount_due)
        self.instance.save()

    @staticmethod
    def get_relaunch(obj):
        """
            Not used since we removed csv
        Args:
            obj: project instance

        Returns:

        """
        previous_revision = models.Project.objects.prefetch_related('batch_files').filter(~Q(id=obj.id),
                                                                                          group_id=obj.group_id) \
            .order_by('-id').first()
        previous_batch_file = previous_revision.batch_files.first() if previous_revision else None
        batch_file = obj.batch_files.first()
        active_workers = models.TaskWorker.objects.active().filter(task__project__group_id=obj.group_id,
                                                                   task__exclude_at__isnull=True,
                                                                   status__in=[models.TaskWorker.STATUS_IN_PROGRESS,
                                                                               models.TaskWorker.STATUS_SUBMITTED,
                                                                               models.TaskWorker.STATUS_RETURNED,
                                                                               models.TaskWorker.STATUS_ACCEPTED]
                                                                   ).count()
        same_file = (
            previous_batch_file is not None and batch_file is not None and
            previous_batch_file.id == batch_file.id
        )
        different_file = (previous_batch_file is not None and batch_file is None) or \
                         (previous_batch_file is None and batch_file is not None)
        if previous_revision is None or active_workers == 0:
            return {
                "is_forced": False,
                "ask_for_relaunch": False,
                "overlaps": False
            }
        elif (previous_batch_file is None and batch_file is None) or same_file:
            return {
                "is_forced": False,
                "ask_for_relaunch": True,
                "overlaps": True
            }
        elif different_file:
            return {
                "is_forced": True,
                "ask_for_relaunch": False,
                "overlaps": False
            }
        elif previous_batch_file.id != batch_file.id:
            return {
                "is_forced": False,
                "ask_for_relaunch": True,
                "overlaps": True
            }

    def pay(self, amount_due, *args, **kwargs):
        requester_account = models.FinancialAccount.objects.get(owner_id=self.instance.owner_id,
                                                                type=models.FinancialAccount.TYPE_REQUESTER,
                                                                is_system=False).id
        system_account = models.FinancialAccount.objects.get(is_system=True,
                                                             type=models.FinancialAccount.TYPE_ESCROW).id
        transaction_data = {
            'sender': requester_account,
            'recipient': system_account,
            'amount': amount_due,
            'method': 'daemo',
            'sender_type': models.Transaction.TYPE_PROJECT_OWNER,
            'reference': 'P#' + str(self.instance.id)
        }
        if amount_due < 0:
            transaction_data['sender'] = system_account
            transaction_data['recipient'] = requester_account
            transaction_data['amount'] = abs(amount_due)

        transaction_serializer = TransactionSerializer(data=transaction_data)
        if transaction_serializer.is_valid():
            if amount_due != 0:
                transaction_serializer.create()
            self.instance.is_paid = True
            self.instance.save()
        else:
            raise ValidationError('Error in payment')

    @staticmethod
    def get_revisions(obj):
        return models.Project.objects.active().filter(group_id=obj.group_id).order_by('id').values_list('id',
                                                                                                        flat=True)

    def reset_boomerang(self):
        update_project_boomerang.delay(self.instance.id)

    @staticmethod
    def get_hash_id(obj):
        return to_hash(obj.group_id)
예제 #7
0
class ProjectSerializer(DynamicFieldsModelSerializer):
    deleted = serializers.BooleanField(read_only=True)
    templates = TemplateSerializer(many=True, required=False)
    total_tasks = serializers.SerializerMethodField()
    file_id = serializers.IntegerField(write_only=True,
                                       allow_null=True,
                                       required=False)
    age = serializers.SerializerMethodField()
    has_comments = serializers.SerializerMethodField()
    available_tasks = serializers.SerializerMethodField()
    comments = serializers.SerializerMethodField()
    name = serializers.CharField(default='Untitled Project')
    status = serializers.IntegerField(default=1)
    owner = RequesterSerializer(fields=('alias', ), read_only=True)
    batch_files = BatchFileSerializer(many=True,
                                      read_only=True,
                                      fields=(
                                          'id',
                                          'name',
                                          'size',
                                          'column_headers',
                                          'format',
                                          'number_of_rows',
                                      ))
    num_rows = serializers.IntegerField(write_only=True,
                                        allow_null=True,
                                        required=False)
    requester_rating = serializers.FloatField(read_only=True, required=False)
    raw_rating = serializers.IntegerField(read_only=True, required=False)

    class Meta:
        model = models.Project
        fields = (
            'id',
            'name',
            'owner',
            'description',
            'status',
            'repetition',
            'timeout',
            'templates',
            'batch_files',
            'deleted',
            'created_timestamp',
            'last_updated',
            'price',
            'has_data_set',
            'data_set_location',
            'total_tasks',
            'file_id',
            'age',
            'is_micro',
            'is_prototype',
            'task_time',
            'allow_feedback',
            'feedback_permissions',
            'min_rating',
            'has_comments',
            'available_tasks',
            'comments',
            'num_rows',
            'requester_rating',
            'raw_rating',
        )
        read_only_fields = (
            'created_timestamp',
            'last_updated',
            'deleted',
            'owner',
            'has_comments',
            'available_tasks',
            'comments',
            'templates',
        )

    def create(self, **kwargs):
        project = models.Project.objects.create(
            deleted=False, owner=kwargs['owner'].requester)
        template = {"name": 't_' + generate_random_id()}
        template_serializer = TemplateSerializer(data=template)
        template = None
        if template_serializer.is_valid():
            template = template_serializer.create(with_default=True,
                                                  owner=kwargs['owner'])
        else:
            raise ValidationError(template_serializer.errors)
        models.ProjectTemplate.objects.get_or_create(project=project,
                                                     template=template)
        return project

    def delete(self, instance):
        instance.deleted = True
        instance.save()
        return instance

    def get_age(self, model):
        from crowdsourcing.utils import get_time_delta

        if model.status == 1:
            return "Saved " + get_time_delta(model.last_updated)
        else:
            return "Posted " + get_time_delta(model.published_time)

    def get_total_tasks(self, obj):
        return obj.project_tasks.all().count()

    def get_has_comments(self, obj):
        return obj.projectcomment_project.count() > 0

    def get_available_tasks(self, obj):
        available_task_count = models.Project.objects.values('id').raw(
            '''
          select count(*) id from (
            SELECT
              "crowdsourcing_task"."id"
            FROM "crowdsourcing_task"
              INNER JOIN "crowdsourcing_project" ON ("crowdsourcing_task"."project_id" = "crowdsourcing_project"."id")
              LEFT OUTER JOIN "crowdsourcing_taskworker" ON ("crowdsourcing_task"."id" =
                "crowdsourcing_taskworker"."task_id" and task_status not in (4,6))
            WHERE ("crowdsourcing_task"."project_id" = %s AND NOT (
              ("crowdsourcing_task"."id" IN (SELECT U1."task_id" AS Col1
              FROM "crowdsourcing_taskworker" U1 WHERE U1."worker_id" = %s and U1.task_status<>6))))
            GROUP BY "crowdsourcing_task"."id", "crowdsourcing_project"."repetition"
            HAVING "crowdsourcing_project"."repetition" > (COUNT("crowdsourcing_taskworker"."id"))) available_tasks
            ''',
            params=[
                obj.id, self.context['request'].user.userprofile.worker.id
            ])[0].id
        return available_task_count

    def get_comments(self, obj):
        if obj:
            comments = []
            tasks = obj.project_tasks.all()
            for task in tasks:
                task_comments = task.taskcomment_task.all()
                for task_comment in task_comments:
                    comments.append(task_comment)
            serializer = TaskCommentSerializer(many=True,
                                               instance=comments,
                                               read_only=True)
            return serializer.data
        return []

    def update(self, *args, **kwargs):
        status = self.validated_data.get('status', self.instance.status)
        num_rows = self.validated_data.get('num_rows', 0)
        if self.instance.status != status and status == 2:
            if self.instance.templates.all()[0].template_items.count() == 0:
                raise ValidationError('At least one template item is required')
            if self.instance.batch_files.count() == 0:
                task_data = {
                    "project": self.instance.id,
                    "status": 1,
                    "data": {}
                }
                task_serializer = TaskSerializer(data=task_data)
                if task_serializer.is_valid():
                    task_serializer.create()
                else:
                    raise ValidationError(task_serializer.errors)
            else:
                batch_file = self.instance.batch_files.first()
                data = batch_file.parse_csv()
                count = 0
                for row in data:
                    if count == num_rows:
                        break
                    task = {'project': self.instance.id, 'data': row}
                    task_serializer = TaskSerializer(data=task)
                    if task_serializer.is_valid():
                        task_serializer.create(**kwargs)
                        count += 1
                    else:
                        raise ValidationError(task_serializer.errors)
            self.instance.published_time = datetime.now()
            status += 1

        self.instance.name = self.validated_data.get('name',
                                                     self.instance.name)
        self.instance.price = self.validated_data.get('price',
                                                      self.instance.price)
        self.instance.repetition = self.validated_data.get(
            'repetition', self.instance.repetition)
        self.instance.status = status
        self.instance.save()
        return self.instance

    def fork(self, *args, **kwargs):
        templates = self.instance.templates.all()
        categories = self.instance.categories.all()
        batch_files = self.instance.batch_files.all()

        project = self.instance
        project.name = project.name + ' (copy)'
        project.status = 1
        project.is_prototype = False
        project.parent = models.Project.objects.get(pk=self.instance.id)
        project.id = None
        project.save()

        for template in templates:
            project_template = models.ProjectTemplate(project=project,
                                                      template=template)
            project_template.save()
        for category in categories:
            project_category = models.ProjectCategory(project=project,
                                                      category=category)
            project_category.save()
        for batch_file in batch_files:
            project_batch_file = models.ProjectBatchFile(project=project,
                                                         batch_file=batch_file)
            project_batch_file.save()