Exemple #1
0
class AccountSerializer(WritableNestedSerializer):
    """
    Serializer for the account model.
    """
    # Read only fields.
    content_type = ContentTypeSerializer(read_only=True)
    flatname = serializers.CharField(read_only=True)
    contacts = ContactForAccountSerializer(many=True, read_only=True)

    # Related fields
    addresses = RelatedAddressSerializer(many=True, required=False, create_only=True)
    assigned_to = RelatedLilyUserSerializer(required=False, assign_only=True)
    email_addresses = RelatedEmailAddressSerializer(many=True, required=False, create_only=True)
    phone_numbers = RelatedPhoneNumberSerializer(many=True, required=False, create_only=True)
    social_media = RelatedSocialMediaSerializer(many=True, required=False, create_only=True)
    websites = RelatedWebsiteSerializer(many=True, required=False, create_only=True)
    status = RelatedAccountStatusSerializer(assign_only=True)
    tags = RelatedTagSerializer(many=True, required=False, create_only=True)

    # Custom fields.
    name = serializers.CharField(validators=[DuplicateAccountName()])
    description = SanitizedHtmlCharField()

    class Meta:
        model = Account
        fields = (
            'addresses',
            'assigned_to',
            'bankaccountnumber',
            'bic',
            'cocnumber',
            'contacts',
            'content_type',
            'customer_id',
            'description',
            'email_addresses',
            'flatname',
            'iban',
            'legalentity',
            # 'logo',
            'id',
            'modified',
            'name',
            'phone_numbers',
            'social_media',
            'status',
            'tags',
            'taxnumber',
            'websites',
        )
Exemple #2
0
class AccountSerializer(PhoneNumberFormatMixin, WritableNestedSerializer):
    """
    Serializer for the account model.
    """
    # Read only fields.
    content_type = ContentTypeSerializer(read_only=True)
    flatname = serializers.CharField(read_only=True)
    contacts = ContactForAccountSerializer(many=True, read_only=True)

    # Related fields
    addresses = RelatedAddressSerializer(many=True,
                                         required=False,
                                         create_only=True)
    assigned_to = RelatedLilyUserSerializer(required=False, assign_only=True)
    email_addresses = RelatedEmailAddressSerializer(many=True,
                                                    required=False,
                                                    create_only=True)
    phone_numbers = RelatedPhoneNumberSerializer(many=True,
                                                 required=False,
                                                 create_only=True)
    social_media = RelatedSocialMediaSerializer(many=True,
                                                required=False,
                                                create_only=True)
    websites = RelatedWebsiteSerializer(many=True,
                                        required=False,
                                        create_only=True)
    status = RelatedAccountStatusSerializer(assign_only=True)
    tags = RelatedTagSerializer(many=True, required=False, create_only=True)

    # Custom fields.
    name = serializers.CharField(validators=[DuplicateAccountName()])
    description = SanitizedHtmlCharField()

    def create(self, validated_data):
        tenant = self.context.get('request').user.tenant
        account_count = Account.objects.filter(is_deleted=False).count()

        if tenant.billing.is_free_plan and account_count >= settings.FREE_PLAN_ACCOUNT_CONTACT_LIMIT:
            raise serializers.ValidationError({
                'limit_reached':
                _('You\'ve reached the limit of accounts for the free plan.'),
            })

        instance = super(AccountSerializer, self).create(validated_data)

        return instance

    class Meta:
        model = Account
        fields = (
            'id',
            'addresses',
            'assigned_to',
            'bankaccountnumber',
            'bic',
            'cocnumber',
            'contacts',
            'content_type',
            'customer_id',
            'description',
            'email_addresses',
            'flatname',
            'iban',
            'is_deleted',
            'legalentity',
            # 'logo',
            'modified',
            'name',
            'phone_numbers',
            'social_media',
            'status',
            'tags',
            'taxnumber',
            'websites',
        )
        read_only_fields = ('is_deleted', )
Exemple #3
0
class CaseSerializer(WritableNestedSerializer):
    """
    Serializer for the case model.
    """
    # Set non mutable fields.
    created = serializers.DateTimeField(read_only=True)
    created_by = RelatedLilyUserSerializer(read_only=True)
    modified = serializers.DateTimeField(read_only=True)
    content_type = ContentTypeSerializer(read_only=True)

    # Custom fields.
    description = SanitizedHtmlCharField()

    # Related fields.
    account = RelatedAccountSerializer(required=False, allow_null=True)
    contact = RelatedContactSerializer(required=False, allow_null=True)
    assigned_to = RelatedLilyUserSerializer(required=False,
                                            allow_null=True,
                                            assign_only=True)
    assigned_to_teams = RelatedTeamSerializer(many=True,
                                              required=False,
                                              assign_only=True)
    type = RelatedCaseTypeSerializer(assign_only=True)
    status = RelatedCaseStatusSerializer(assign_only=True)
    tags = RelatedTagSerializer(many=True, required=False, create_only=True)

    # Show string versions of fields.
    priority_display = serializers.CharField(source='get_priority_display',
                                             read_only=True)

    def validate(self, attrs):
        contact_id = attrs.get('contact', {})
        if isinstance(contact_id, dict):
            contact_id = contact_id.get('id')

        account_id = attrs.get('account', {})
        if isinstance(account_id, dict):
            account_id = account_id.get('id')

        if contact_id and account_id:
            if not Function.objects.filter(contact_id=contact_id,
                                           account_id=account_id).exists():
                raise serializers.ValidationError(
                    {'contact': _('Given contact must work at the account.')})

        return super(CaseSerializer, self).validate(attrs)

    def create(self, validated_data):
        user = self.context.get('request').user
        assigned_to = validated_data.get('assigned_to')

        validated_data.update({
            'created_by_id': user.pk,
        })

        if assigned_to:
            Group('tenant-%s' % user.tenant.id).send({
                'text':
                anyjson.dumps({
                    'event': 'case-assigned',
                }),
            })

            if assigned_to.get('id') != user.pk:
                validated_data.update({
                    'newly_assigned': True,
                })

        else:
            Group('tenant-%s' % user.tenant.id).send({
                'text':
                anyjson.dumps({
                    'event': 'case-unassigned',
                }),
            })

        return super(CaseSerializer, self).create(validated_data)

    def update(self, instance, validated_data):
        user = self.context.get('request').user
        status_id = validated_data.get('status', instance.status_id)
        assigned_to = validated_data.get('assigned_to')

        if assigned_to:
            assigned_to = assigned_to.get('id')

        if isinstance(status_id, dict):
            status_id = status_id.get('id')

        status = CaseStatus.objects.get(pk=status_id)

        # Automatically archive the case if the status is set to 'Closed'.
        if status.name == 'Closed' and 'is_archived' not in validated_data:
            validated_data.update({'is_archived': True})

        # Check if the case being reassigned. If so we want to notify that user.
        if assigned_to and assigned_to != user.pk:
            validated_data.update({
                'newly_assigned': True,
            })
        elif 'assigned_to' in validated_data and not assigned_to:
            # Case is unassigned, so clear newly assigned flag.
            validated_data.update({
                'newly_assigned': False,
            })

        if 'assigned_to' in validated_data or instance.assigned_to_id:
            Group('tenant-%s' % user.tenant.id).send({
                'text':
                anyjson.serialize({
                    'event': 'case-assigned',
                }),
            })

        if (not instance.assigned_to_id
                or instance.assigned_to_id and 'assigned_to' in validated_data
                and not validated_data.get('assigned_to')):
            Group('tenant-%s' % user.tenant.id).send({
                'text':
                anyjson.serialize({
                    'event': 'case-unassigned',
                }),
            })

        return super(CaseSerializer, self).update(instance, validated_data)

    class Meta:
        model = Case
        fields = (
            'id',
            'account',
            'assigned_to',
            'assigned_to_teams',
            'contact',
            'content_type',
            'created',
            'created_by',
            'description',
            'expires',
            'is_archived',
            'modified',
            'newly_assigned',
            'priority',
            'priority_display',
            'status',
            'tags',
            'subject',
            'type',
        )
Exemple #4
0
class DealSerializer(WritableNestedSerializer):
    """
    Serializer for the deal model.
    """
    # Set non mutable fields.
    created_by = RelatedLilyUserSerializer(read_only=True)
    content_type = ContentTypeSerializer(
        read_only=True,
        help_text='This is what the object is identified as in the back-end.',
    )

    # Custom fields.
    description = SanitizedHtmlCharField(
        help_text='Any extra text to describe the deal (supports Markdown).', )

    # Related fields.
    account = RelatedAccountSerializer(
        required=False,
        allow_null=True,
        help_text='Account for which the deal is being created.',
    )
    contact = RelatedContactSerializer(
        required=False,
        allow_null=True,
        help_text='Contact for which the deal is being created.',
    )
    assigned_to = RelatedLilyUserSerializer(
        required=False,
        allow_null=True,
        assign_only=True,
        help_text='Person which the deal is assigned to.',
    )
    assigned_to_teams = RelatedTeamSerializer(
        many=True,
        required=False,
        assign_only=True,
        help_text='List of teams the deal is assigned to.',
    )
    next_step = RelatedDealNextStepSerializer(
        assign_only=True,
        help_text=
        'Allows the user to set what the next action is for the deal.',
    )
    tags = RelatedTagSerializer(
        many=True,
        required=False,
        create_only=True,
        help_text='Any tags used to further categorize the deal.',
    )
    why_lost = RelatedDealWhyLostSerializer(
        assign_only=True,
        allow_null=True,
        required=False,
        help_text='Allows the user to set why the deal was lost.',
    )
    why_customer = RelatedDealWhyCustomerSerializer(
        assign_only=True,
        allow_null=True,
        required=False,
        help_text='Why does someone want to become a customer.',
    )
    found_through = RelatedDealFoundThroughSerializer(
        assign_only=True,
        allow_null=True,
        required=False,
        help_text='How did the customer find your company.',
    )
    contacted_by = RelatedDealContactedBySerializer(
        assign_only=True,
        allow_null=True,
        required=False,
        help_text='How did the customer contact your company.',
    )
    status = RelatedDealStatusSerializer(
        assign_only=True,
        help_text='The status of the deal.',
    )

    # Show string versions of fields.
    currency_display = serializers.CharField(source='get_currency_display',
                                             read_only=True)

    amount_once = RegexDecimalField(
        max_digits=19,
        decimal_places=2,
        required=True,
        help_text='One time payment for the deal.',
    )
    amount_recurring = RegexDecimalField(
        max_digits=19,
        decimal_places=2,
        required=True,
        help_text='Recurring costs for the deal.',
    )

    def validate(self, data):
        new_business = data.get('new_business')
        why_customer = data.get('why_customer')
        found_through = data.get('found_through')
        contacted_by = data.get('contacted_by')

        if new_business:
            errors = {}

            if not found_through:
                errors.update(
                    {'found_through': _('This field may not be empty.')})

            if not contacted_by:
                errors.update(
                    {'contacted_by': _('This field may not be empty.')})

            if not why_customer:
                errors.update(
                    {'why_customer': _('This field may not be empty.')})

            if errors:
                raise serializers.ValidationError(errors)

        contact_id = data.get('contact', {})
        if isinstance(contact_id, dict):
            contact_id = contact_id.get('id')

        account_id = data.get('account', {})
        if isinstance(account_id, dict):
            account_id = account_id.get('id')

        if contact_id and account_id:
            if not Function.objects.filter(contact_id=contact_id,
                                           account_id=account_id).exists():
                raise serializers.ValidationError(
                    {'contact': _('Given contact must work at the account.')})

        # Check if we are related and if we only passed in the id, which means user just wants new reference.
        errors = {
            'account': _('Please enter an account and/or contact.'),
            'contact': _('Please enter an account and/or contact.'),
        }

        if not self.partial:
            # For POST or PUT we always want to check if either is set.
            if not (account_id or contact_id):
                raise serializers.ValidationError(errors)
        else:
            # For PATCH only check the data if both account and contact are passed.
            if ('account' in data
                    and 'contact' in data) and not (account_id or contact_id):
                raise serializers.ValidationError(errors)

        status_id = data.get('status', {})
        if isinstance(status_id, dict):
            status_id = status_id.get('id')

        why_lost_id = data.get('why_lost', {})
        if isinstance(why_lost_id, dict):
            why_lost_id = why_lost_id.get('id')

        if status_id:
            status = DealStatus.objects.get(pk=status_id)

            if status.is_lost and why_lost_id is None and DealWhyLost.objects.exists(
            ):
                raise serializers.ValidationError(
                    {'why_lost': _('This field may not be empty.')})

        return super(DealSerializer, self).validate(data)

    def create(self, validated_data):
        user = self.context.get('request').user
        status_id = validated_data.get('status').get('id')
        status = DealStatus.objects.get(pk=status_id)
        closed_date = validated_data.get('closed_date')

        # Set closed_date if status is lost/won and not manually provided.
        if (status.is_won or status.is_lost) and not closed_date:
            closed_date = datetime.datetime.utcnow().replace(tzinfo=utc)
        else:
            closed_date = None

        validated_data.update({
            'created_by_id': user.pk,
            'closed_date': closed_date,
        })

        assigned_to = validated_data.get('assigned_to')

        if assigned_to:
            Group('tenant-%s' % user.tenant.id).send({
                'text':
                anyjson.serialize({
                    'event': 'deal-assigned',
                }),
            })

            if assigned_to.get('id') != user.pk:
                validated_data.update({
                    'newly_assigned': True,
                })

        else:
            Group('tenant-%s' % user.tenant.id).send({
                'text':
                anyjson.serialize({
                    'event': 'deal-unassigned',
                }),
            })

        instance = super(DealSerializer, self).create(validated_data)

        # Track newly ceated accounts in segment.
        if not settings.TESTING:
            analytics.track(
                user.id,
                'deal-created',
                {
                    'assigned_to_id':
                    instance.assigned_to_id if instance.assigned_to else '',
                    'status':
                    instance.status.name,
                    'next_step':
                    instance.next_step.name,
                    'creation_type':
                    'automatic' if is_external_referer(
                        self.context.get('request')) else 'manual',
                },
            )

        return instance

    def update(self, instance, validated_data):
        user = self.context.get('request').user
        status_id = validated_data.get('status', instance.status_id)
        next_step = validated_data.get('next_step')

        if isinstance(status_id, dict):
            status_id = status_id.get('id')

        status = DealStatus.objects.get(pk=status_id)
        closed_date = validated_data.get('closed_date', instance.closed_date)
        assigned_to = validated_data.get('assigned_to')

        if assigned_to:
            assigned_to = assigned_to.get('id')

        # Set closed_date after changing status to lost/won and reset it when it's any other status.
        if status.is_won or status.is_lost:
            if not closed_date:
                closed_date = datetime.datetime.utcnow().replace(tzinfo=utc)
        else:
            closed_date = None

        validated_data.update({
            'closed_date': closed_date,
        })

        # Check if the deal is being reassigned. If so we want to notify that user.
        if assigned_to and assigned_to != user.pk:
            validated_data.update({
                'newly_assigned': True,
            })
        elif 'assigned_to' in validated_data and not assigned_to:
            # Deal is unassigned, so clear newly assigned flag.
            validated_data.update({
                'newly_assigned': False,
            })

        if (('status' in validated_data and status.name == 'Open')
                or ('is_archived' in validated_data
                    and not validated_data.get('is_archived'))):
            # Deal is reopened or unarchived, so we want to notify the user again.
            validated_data.update({
                'newly_assigned': True,
            })

        try:
            none_step = DealNextStep.objects.get(name='None')
        except DealNextStep.DoesNotExist:
            pass

        if next_step:
            try:
                next_step = DealNextStep.objects.get(pk=next_step.get('id'))
            except DealNextStep.DoesNotExist:
                raise serializers.ValidationError(
                    {'why_lost': _('This field may not be empty.')})
            else:
                if next_step.date_increment != 0:
                    validated_data.update({
                        'next_step_date':
                        add_business_days(next_step.date_increment),
                    })
                elif none_step and next_step.id == none_step.id:
                    validated_data.update({
                        'next_step_date': None,
                    })

        if 'assigned_to' in validated_data or instance.assigned_to_id:
            Group('tenant-%s' % user.tenant.id).send({
                'text':
                anyjson.serialize({
                    'event': 'deal-assigned',
                }),
            })

        if (not instance.assigned_to_id
                or instance.assigned_to_id and 'assigned_to' in validated_data
                and not validated_data.get('assigned_to')):
            Group('tenant-%s' % user.tenant.id).send({
                'text':
                anyjson.serialize({
                    'event': 'deal-unassigned',
                }),
            })

        return super(DealSerializer, self).update(instance, validated_data)

    class Meta:
        model = Deal
        fields = (
            'id',
            'account',
            'amount_once',
            'amount_recurring',
            'assigned_to',
            'assigned_to_teams',
            'card_sent',
            'closed_date',
            'contact',
            'contacted_by',
            'content_type',
            'created',
            'created_by',
            'currency',
            'currency_display',
            'description',
            'found_through',
            'is_archived',
            'is_checked',
            'is_deleted',
            'modified',
            'name',
            'new_business',
            'newly_assigned',
            'next_step',
            'next_step_date',
            'quote_id',
            'status',
            'tags',
            'twitter_checked',
            'why_customer',
            'why_lost',
        )
        extra_kwargs = {
            'created': {
                'help_text':
                'Shows the date and time when the deal was created.',
            },
            'modified': {
                'help_text':
                'Shows the date and time when the deal was last modified.',
            },
            'next_step_date': {
                'help_text':
                'Shows the date and time for when the next step should be executed.',
            },
            'closed_date': {
                'help_text':
                'Shows the date and time when the deal was set to \'Won\' or \'Closed\'.',
            },
            'newly_assigned': {
                'help_text':
                'True if the assignee was changed and that person hasn\'t accepted yet.',
            },
            'name': {
                'help_text': 'A short description of the deal.',
            },
        }
Exemple #5
0
class DealSerializer(WritableNestedSerializer):
    """
    Serializer for the deal model.
    """
    # Set non mutable fields.
    created = serializers.DateTimeField(read_only=True)
    created_by = RelatedLilyUserSerializer(read_only=True)
    modified = serializers.DateTimeField(read_only=True)
    content_type = ContentTypeSerializer(read_only=True)

    # Custom fields.
    description = SanitizedHtmlCharField()

    # Related fields.
    account = RelatedAccountSerializer()
    contact = RelatedContactSerializer(required=False, allow_null=True)
    assigned_to = RelatedLilyUserSerializer(required=False,
                                            allow_null=True,
                                            assign_only=True)
    assigned_to_teams = RelatedTeamSerializer(many=True,
                                              required=False,
                                              assign_only=True)
    next_step = RelatedDealNextStepSerializer(assign_only=True)
    tags = RelatedTagSerializer(many=True, required=False, create_only=True)
    why_lost = RelatedDealWhyLostSerializer(assign_only=True,
                                            allow_null=True,
                                            required=False)
    why_customer = RelatedDealWhyCustomerSerializer(assign_only=True,
                                                    allow_null=True,
                                                    required=False)
    found_through = RelatedDealFoundThroughSerializer(assign_only=True,
                                                      allow_null=True,
                                                      required=False)
    contacted_by = RelatedDealContactedBySerializer(assign_only=True,
                                                    allow_null=True,
                                                    required=False)
    status = RelatedDealStatusSerializer(assign_only=True)

    # Show string versions of fields.
    currency_display = serializers.CharField(source='get_currency_display',
                                             read_only=True)

    amount_once = RegexDecimalField(max_digits=19,
                                    decimal_places=2,
                                    required=True)
    amount_recurring = RegexDecimalField(max_digits=19,
                                         decimal_places=2,
                                         required=True)

    def validate(self, attrs):
        new_business = attrs.get('new_business')
        why_customer = attrs.get('why_customer')
        found_through = attrs.get('found_through')
        contacted_by = attrs.get('contacted_by')

        if new_business:
            errors = {}

            if not found_through:
                errors.update(
                    {'found_through': _('This field may not be empty.')})

            if not contacted_by:
                errors.update(
                    {'contacted_by': _('This field may not be empty.')})

            if not why_customer:
                errors.update(
                    {'why_customer': _('This field may not be empty.')})

            if errors:
                raise serializers.ValidationError(errors)

        contact_id = attrs.get('contact', {})
        if isinstance(contact_id, dict):
            contact_id = contact_id.get('id')

        account_id = attrs.get('account', {})
        if isinstance(account_id, dict):
            account_id = account_id.get('id')

        if contact_id and account_id:
            if not Function.objects.filter(contact_id=contact_id,
                                           account_id=account_id).exists():
                raise serializers.ValidationError(
                    {'contact': _('Given contact must work at the account.')})

        status_id = attrs.get('status', {})
        if isinstance(status_id, dict):
            status_id = status_id.get('id')

        why_lost_id = attrs.get('why_lost', {})
        if isinstance(why_lost_id, dict):
            why_lost_id = why_lost_id.get('id')

        if status_id:
            status = DealStatus.objects.get(pk=status_id)

            if status.is_lost and why_lost_id is None and DealWhyLost.objects.exists(
            ):
                raise serializers.ValidationError(
                    {'why_lost': _('This field may not be empty.')})

        return super(DealSerializer, self).validate(attrs)

    def create(self, validated_data):
        user = self.context.get('request').user
        status_id = validated_data.get('status').get('id')
        status = DealStatus.objects.get(pk=status_id)
        closed_date = validated_data.get('closed_date')

        # Set closed_date if status is lost/won and not manually provided.
        if (status.is_won or status.is_lost) and not closed_date:
            closed_date = datetime.datetime.utcnow().replace(tzinfo=utc)
        else:
            closed_date = None

        validated_data.update({
            'created_by_id': user.pk,
            'closed_date': closed_date,
        })

        assigned_to = validated_data.get('assigned_to')

        if assigned_to:
            Group('tenant-%s' % user.tenant.id).send({
                'text':
                anyjson.serialize({
                    'event': 'deal-assigned',
                }),
            })

            if assigned_to.get('id') != user.pk:
                validated_data.update({
                    'newly_assigned': True,
                })

        else:
            Group('tenant-%s' % user.tenant.id).send({
                'text':
                anyjson.serialize({
                    'event': 'deal-unassigned',
                }),
            })

        return super(DealSerializer, self).create(validated_data)

    def update(self, instance, validated_data):
        user = self.context.get('request').user
        status_id = validated_data.get('status', instance.status_id)
        next_step = validated_data.get('next_step')

        if isinstance(status_id, dict):
            status_id = status_id.get('id')

        status = DealStatus.objects.get(pk=status_id)
        closed_date = validated_data.get('closed_date', instance.closed_date)
        assigned_to = validated_data.get('assigned_to')

        if assigned_to:
            assigned_to = assigned_to.get('id')

        # Set closed_date after changing status to lost/won and reset it when it's any other status.
        if status.is_won or status.is_lost:
            if not closed_date:
                closed_date = datetime.datetime.utcnow().replace(tzinfo=utc)
        else:
            closed_date = None

        validated_data.update({
            'closed_date': closed_date,
        })

        # Check if the deal is being reassigned. If so we want to notify that user.
        if assigned_to and assigned_to != user.pk:
            validated_data.update({
                'newly_assigned': True,
            })
        elif 'assigned_to' in validated_data and not assigned_to:
            # Deal is unassigned, so clear newly assigned flag.
            validated_data.update({
                'newly_assigned': False,
            })

        try:
            none_step = DealNextStep.objects.get(name='None')
        except DealNextStep.DoesNotExist:
            pass

        if next_step:
            try:
                next_step = DealNextStep.objects.get(pk=next_step.get('id'))
            except DealNextStep.DoesNotExist:
                raise serializers.ValidationError(
                    {'why_lost': _('This field may not be empty.')})
            else:
                if next_step.date_increment != 0:
                    validated_data.update({
                        'next_step_date':
                        add_business_days(next_step.date_increment),
                    })
                elif none_step and next_step.id == none_step.id:
                    validated_data.update({
                        'next_step_date': None,
                    })

        if 'assigned_to' in validated_data or instance.assigned_to_id:
            Group('tenant-%s' % user.tenant.id).send({
                'text':
                anyjson.serialize({
                    'event': 'deal-assigned',
                }),
            })

        if (not instance.assigned_to_id
                or instance.assigned_to_id and 'assigned_to' in validated_data
                and not validated_data.get('assigned_to')):
            Group('tenant-%s' % user.tenant.id).send({
                'text':
                anyjson.serialize({
                    'event': 'deal-unassigned',
                }),
            })

        return super(DealSerializer, self).update(instance, validated_data)

    class Meta:
        model = Deal
        fields = (
            'id',
            'account',
            'amount_once',
            'amount_recurring',
            'assigned_to',
            'assigned_to_teams',
            'card_sent',
            'closed_date',
            'contact',
            'contacted_by',
            'content_type',
            'created',
            'created_by',
            'currency',
            'currency_display',
            'description',
            'found_through',
            'is_archived',
            'is_checked',
            'is_deleted',
            'modified',
            'name',
            'new_business',
            'newly_assigned',
            'next_step',
            'next_step_date',
            'quote_id',
            'status',
            'tags',
            'twitter_checked',
            'why_customer',
            'why_lost',
        )
Exemple #6
0
class AccountSerializer(PhoneNumberFormatMixin, WritableNestedSerializer):
    """
    Serializer for the account model.
    """
    # Read only fields.
    content_type = ContentTypeSerializer(
        read_only=True,
        help_text='This is what the object is identified as in the back-end.')
    contacts = ContactForAccountSerializer(
        many=True,
        read_only=True,
        help_text='Contains all contacts which work for this account.',
    )

    # Related fields
    addresses = RelatedAddressSerializer(
        many=True,
        required=False,
        create_only=True,
        help_text='Addresses belonging to the account.',
    )
    assigned_to = RelatedLilyUserSerializer(
        required=False,
        allow_null=True,
        assign_only=True,
        help_text='Person which the account is assigned to.',
    )
    email_addresses = RelatedEmailAddressSerializer(
        many=True,
        required=False,
        create_only=True,
        help_text='Email addresses belonging to the account.',
    )
    phone_numbers = RelatedPhoneNumberSerializer(
        many=True,
        required=False,
        create_only=True,
        help_text='Phone numbers belonging to the account.',
    )
    social_media = RelatedSocialMediaSerializer(
        many=True,
        required=False,
        create_only=True,
        help_text='Social media accounts belonging to the account.',
    )
    websites = RelatedWebsiteSerializer(
        many=True,
        required=False,
        create_only=True,
        help_text='Any websites that belong to the account.',
    )
    status = RelatedAccountStatusSerializer(
        assign_only=True,
        help_text='ID of an AccountStatus instance.',
    )
    tags = RelatedTagSerializer(
        many=True,
        required=False,
        create_only=True,
        help_text='Any tags used to further categorize the account.',
    )

    # Custom fields.
    name = serializers.CharField(
        validators=[DuplicateAccountName()],
        help_text='The name of the account.',
    )
    description = SanitizedHtmlCharField(
        help_text='Any extra text to describe the account (supports Markdown).',
    )

    def create(self, validated_data):
        tenant = self.context.get('request').user.tenant
        account_count = Account.objects.filter(is_deleted=False).count()

        if tenant.billing.is_free_plan and account_count >= settings.FREE_PLAN_ACCOUNT_CONTACT_LIMIT:
            raise serializers.ValidationError({
                'limit_reached':
                _('You\'ve reached the limit of accounts for the free plan.'),
            })

        instance = super(AccountSerializer, self).create(validated_data)

        # Track newly ceated accounts in segment.
        if not settings.TESTING:
            analytics.track(
                self.context.get('request').user.id,
                'account-created',
                {
                    'assigned_to_id':
                    instance.assigned_to.id if instance.assigned_to else '',
                    'creation_type':
                    'automatic' if is_external_referer(
                        self.context.get('request')) else 'manual',
                },
            )

        return instance

    class Meta:
        model = Account
        fields = (
            'id',
            'addresses',
            'assigned_to',
            'contacts',
            'content_type',
            'created',
            'customer_id',
            'description',
            'email_addresses',
            'is_deleted',
            'modified',
            'name',
            'phone_numbers',
            'social_media',
            'status',
            'tags',
            'websites',
        )
        read_only_fields = ('is_deleted', )
        extra_kwargs = {
            'created': {
                'help_text':
                'Shows the date and time when the account was created.',
            },
            'modified': {
                'help_text':
                'Shows the date and time when the account was last modified.',
            },
            'customer_id': {
                'help_text':
                'An extra identifier for the account which can be used to link to an external source.',
            },
        }
Exemple #7
0
class CaseSerializer(WritableNestedSerializer):
    """
    Serializer for the case model.
    """
    # Set non mutable fields.
    created_by = RelatedLilyUserSerializer(read_only=True)
    content_type = ContentTypeSerializer(
        read_only=True,
        help_text='This is what the object is identified as in the back-end.',
    )

    # Related fields.
    account = RelatedAccountSerializer(
        required=False,
        allow_null=True,
        help_text='Account for which the case is being created.',
    )
    contact = RelatedContactSerializer(
        required=False,
        allow_null=True,
        help_text='Contact for which the case is being created.',
    )
    assigned_to = RelatedLilyUserSerializer(
        required=False,
        allow_null=True,
        assign_only=True,
        help_text='Person which the case is assigned to.',
    )
    assigned_to_teams = RelatedTeamSerializer(
        many=True,
        required=False,
        assign_only=True,
        help_text='List of teams the case is assigned to.',
    )
    type = RelatedCaseTypeSerializer(
        assign_only=True,
        help_text='The type of case.',
    )
    status = RelatedCaseStatusSerializer(
        assign_only=True,
        help_text='Status of the case.',
    )
    tags = RelatedTagSerializer(
        many=True,
        required=False,
        create_only=True,
        help_text='Any tags used to further categorize the case.',
    )

    description = SanitizedHtmlCharField(
        help_text='Any extra text to describe the case (supports Markdown).', )

    # Show string versions of fields.
    priority_display = serializers.CharField(
        source='get_priority_display',
        read_only=True,
        help_text='Human readable value of the case\'s priority.',
    )

    def validate(self, data):
        contact_id = data.get('contact', {})
        if isinstance(contact_id, dict):
            contact_id = contact_id.get('id')

        account_id = data.get('account', {})
        if isinstance(account_id, dict):
            account_id = account_id.get('id')

        if contact_id and account_id:
            if not Function.objects.filter(contact_id=contact_id,
                                           account_id=account_id).exists():
                raise serializers.ValidationError(
                    {'contact': _('Given contact must work at the account.')})

        # Check if we are related and if we only passed in the id, which means user just wants new reference.
        errors = {
            'account': _('Please enter an account and/or contact.'),
            'contact': _('Please enter an account and/or contact.'),
        }

        if not self.partial:
            # For POST or PUT we always want to check if either is set.
            if not (account_id or contact_id):
                raise serializers.ValidationError(errors)
        else:
            # For PATCH only check the data if both account and contact are passed.
            if ('account' in data
                    and 'contact' in data) and not (account_id or contact_id):
                raise serializers.ValidationError(errors)

        return super(CaseSerializer, self).validate(data)

    def create(self, validated_data):
        user = self.context.get('request').user
        assigned_to = validated_data.get('assigned_to')

        validated_data.update({
            'created_by_id': user.pk,
        })

        if assigned_to:
            Group('tenant-%s' % user.tenant.id).send({
                'text':
                anyjson.dumps({
                    'event': 'case-assigned',
                }),
            })

            if assigned_to.get('id') != user.pk:
                validated_data.update({
                    'newly_assigned': True,
                })

        else:
            Group('tenant-%s' % user.tenant.id).send({
                'text':
                anyjson.dumps({
                    'event': 'case-unassigned',
                }),
            })

        instance = super(CaseSerializer, self).create(validated_data)

        # Track newly ceated accounts in segment.
        if not settings.TESTING:
            analytics.track(
                user.id,
                'case-created',
                {
                    'expires':
                    instance.expires,
                    'assigned_to_id':
                    instance.assigned_to_id if instance.assigned_to else '',
                    'creation_type':
                    'automatic' if is_external_referer(
                        self.context.get('request')) else 'manual',
                },
            )

        return instance

    def update(self, instance, validated_data):
        user = self.context.get('request').user
        status_id = validated_data.get('status', instance.status_id)
        assigned_to = validated_data.get('assigned_to')

        if assigned_to:
            assigned_to = assigned_to.get('id')

        if isinstance(status_id, dict):
            status_id = status_id.get('id')

        status = CaseStatus.objects.get(pk=status_id)

        # Automatically archive the case if the status is set to 'Closed'.
        if status.name == 'Closed' and 'is_archived' not in validated_data:
            validated_data.update({'is_archived': True})

        # Check if the case being reassigned. If so we want to notify that user.
        if assigned_to and assigned_to != user.pk:
            validated_data.update({
                'newly_assigned': True,
            })
        elif 'assigned_to' in validated_data and not assigned_to:
            # Case is unassigned, so clear newly assigned flag.
            validated_data.update({
                'newly_assigned': False,
            })

        if (('status' in validated_data and status.name == 'Open')
                or ('is_archived' in validated_data
                    and not validated_data.get('is_archived'))):
            # Case is reopened or unarchived, so we want to notify the user again.
            validated_data.update({
                'newly_assigned': True,
            })

        if 'assigned_to' in validated_data or instance.assigned_to_id:
            Group('tenant-%s' % user.tenant.id).send({
                'text':
                anyjson.serialize({
                    'event': 'case-assigned',
                }),
            })

        if (not instance.assigned_to_id
                or instance.assigned_to_id and 'assigned_to' in validated_data
                and not validated_data.get('assigned_to')):
            Group('tenant-%s' % user.tenant.id).send({
                'text':
                anyjson.serialize({
                    'event': 'case-unassigned',
                }),
            })

        return super(CaseSerializer, self).update(instance, validated_data)

    class Meta:
        model = Case
        fields = (
            'id',
            'account',
            'assigned_to',
            'assigned_to_teams',
            'contact',
            'content_type',
            'created',
            'created_by',
            'description',
            'expires',
            'is_archived',
            'modified',
            'newly_assigned',
            'priority',
            'priority_display',
            'status',
            'tags',
            'subject',
            'type',
        )
        extra_kwargs = {
            'created': {
                'help_text':
                'Shows the date and time when the deal was created.',
            },
            'expires': {
                'help_text':
                'Shows the date and time for when the case should be completed.',
            },
            'modified': {
                'help_text':
                'Shows the date and time when the case was last modified.',
            },
            'newly_assigned': {
                'help_text':
                'True if the assignee was changed and that person hasn\'t accepted yet.',
            },
            'subject': {
                'help_text': 'A short description of the case.',
            },
        }