示例#1
0
class UserDocument(models.Model):
    user = models.ForeignKey(User, related_name="documents")
    name = models.CharField(_('Document name'),
                            max_length=100,
                            choices=DOCUMENTS.items())
    is_rejected = models.BooleanField(_('Documents was rejected'),
                                      default=False)
    description = models.TextField(_('Document description'),
                                   null=True,
                                   blank=True)

    EXTENSIONS = 'doc', 'docx', 'bmp', 'gif', 'jpg', 'jpeg', 'png', 'pdf'
    FILESIZE_LIMIT = 1024**2 * 5
    file = models.FileField(
        _('File'),
        upload_to=upload_to('files'),
        help_text=_(
            'Supported extensions: %(ext)s. Filesize limit: %(limit)s Mb'),
        validators=[
            allow_file_extensions(EXTENSIONS),
            allow_file_size(FILESIZE_LIMIT)
        ])
    creation_ts = models.DateTimeField(_('Creation timestamp'),
                                       auto_now_add=True)
    is_deleted = models.BooleanField('Document is deleted', default=False)
    fields = JSONField(_('Document fields'), null=True, blank=True)

    objects = UserDocumentManager()

    def __unicode__(self):
        return unicode(DOCUMENTS[self.name])
示例#2
0
class LegalDocument(models.Model):

    name = models.CharField(_('Document name'), max_length=100)
    priority = models.IntegerField(
        _('Priority'),
        help_text=_('Display order from min(first) to max (last)'),
        default=0)
    languages = ArrayField(models.CharField(max_length=10),
                           default=docs_default_languages)

    EXTENSIONS = 'doc', 'docx', 'bmp', 'gif', 'jpg', 'jpeg', 'png', 'pdf', 'xls', 'xlsx'
    FILESIZE_LIMIT = 1024**2 * 15
    FILE_HELP_TEXT = _(
        'Supported extensions: %(ext)s. Filesize limit: %(limit)s Mb') % {
            'ext': ', '.join(EXTENSIONS),
            'limit': '%.2f' % (FILESIZE_LIMIT / 1024.0 / 1024)
        }
    file = models.FileField(_("File"),
                            upload_to=upload_to("legal_documents"),
                            help_text=FILE_HELP_TEXT,
                            validators=[
                                allow_file_extensions(EXTENSIONS),
                                allow_file_size(FILESIZE_LIMIT)
                            ])

    def extension(self):
        name, extension = os.path.splitext(self.file.name)
        return extension

    def __unicode__(self):
        return self.name

    class Meta:
        verbose_name = _("Legal document")
        verbose_name_plural = _("Legal documents")
示例#3
0
class IssueAttachment(models.Model):
    issue = models.ForeignKey(GenericIssue,
                              related_name="attachments",
                              verbose_name=_("issue"))
    user = models.ForeignKey(User, verbose_name=_("user"))

    EXTENSIONS = 'doc', 'docx', 'bmp', 'gif', 'jpg', 'jpeg', 'png', 'pdf', 'xls', 'xlsx'
    FILESIZE_LIMIT = 1024**2 * 5
    FILE_HELP_TEXT = _(
        'Supported extensions: %(ext)s. Filesize limit: %(limit)s Mb') % {
            'ext': ', '.join(EXTENSIONS),
            'limit': '%.2f' % (FILESIZE_LIMIT / 1024.0 / 1024)
        }
    file = models.FileField(_("Attachment"),
                            upload_to=upload_to("issue_attachments"),
                            help_text=FILE_HELP_TEXT,
                            validators=[
                                allow_file_extensions(EXTENSIONS),
                                allow_file_size(FILESIZE_LIMIT)
                            ])

    class Meta:
        verbose_name = _("issue attachment")
        verbose_name_plural = _("issue attachment")

    def __unicode__(self):
        try:
            return os.path.basename(self.file.name)
        except AttributeError:
            return _("<unknown file>")
示例#4
0
class PaymentMethod(models.Model):
    DEPOSIT = 0
    WITHDRAW = 1

    PAYMENT_TYPES = (
        (DEPOSIT, "Deposit"),
        (WITHDRAW, "Withdraw"),
    )

    payment_type = models.PositiveSmallIntegerField(_("Payment type"),
                                                    choices=PAYMENT_TYPES,
                                                    default=0)
    name = models.CharField(_("Name"), max_length=100)
    currency = models.CharField(_("Currency"), max_length=100)
    min_amount = models.DecimalField(_("Minimum amount"),
                                     decimal_places=2,
                                     blank=True,
                                     null=True,
                                     max_digits=10)
    max_amount = models.DecimalField(_("Maximum amount"),
                                     decimal_places=2,
                                     blank=True,
                                     null=True,
                                     max_digits=10)
    commission = models.CharField(_("Commission"),
                                  max_length=150,
                                  blank=True,
                                  default="")

    max_commission = models.CharField(_("Max commision"),
                                      max_length=150,
                                      blank=True,
                                      default="")

    min_commission = models.CharField(_("Min commision"),
                                      max_length=150,
                                      blank=True,
                                      default="")

    processing_times = models.CharField(_("Processing times"),
                                        max_length=150,
                                        blank=True,
                                        default="")
    category = models.ForeignKey(PaymentCategory, verbose_name=_("Category"))
    image = models.ImageField(_('Image'),
                              blank=True,
                              null=True,
                              upload_to=upload_to('payments/logos'),
                              help_text=_('Payment system image'))
    link = models.URLField(_("Link to payment process"))
    languages = ArrayField(models.CharField(max_length=10),
                           default=ps_default_languages)

    def __unicode__(self):
        return self.name

    class Meta:
        verbose_name = _("Payment method")
        verbose_name_plural = _("Payment methods")
示例#5
0
class TemplateAttachment(models.Model):
    """A file attachment for the MessageTemplate"""
    file = models.FileField(upload_to=upload_to('attachments'))
    template = models.ForeignKey(MessageTemplate, related_name='attachments')
    content_id = models.SlugField(
        u'Content id',
        help_text='Used to insert links to the HTML message, for example '
        'Content id = "testimage", &ltimg src="cid:testimage" /&gt')

    def __unicode__(self):
        return u'%s: %s' % (self.template, self.content_id)
示例#6
0
class Article(models.Model):
    '''Статья журнала КИО'''

    authors = models.ManyToManyField(User, verbose_name=u'Авторы')
    rubric = models.ForeignKey(Rubric, verbose_name=u'Рубрика')
    title = models.CharField(u'Заголовок статьи', max_length=256)
    body = models.TextField(u'Текст статьи')
    main_image = models.ImageField(u'Картинка для главной страницы',
                                   upload_to=upload_to('uploads/article/'),
                                   null=True,
                                   blank=True)
    is_ready = models.BooleanField(u'Закончена')
    created_dt = models.DateField(u'дата добавления', auto_now_add=True)
    modified_dt = models.DateField(u'дата обновления', auto_now=True)
    status_db = models.CharField(u'Статус',
                                 max_length=24,
                                 editable=False,
                                 default=u'в процессе')

    objects = ReadyManager()

    def is_published(self):
        for relc in self.releases.all():
            if relc.is_published():
                return True
        return False

    def is_published_display(self):
        if self.releases.exists():
            return u', '.join([r.title for r in self.releases.all()])
        else:
            return u'-нет-'

    is_published_display.short_description = u'выпуски'

    def status(self):
        # опубликована?
        if self.is_published():
            return 'published'
        # запланирована публикация?
        if self.releases.exists():
            return 'approved'
        # автор думает что готово?
        if self.is_ready:
            return 'done'
        #статья в процессе
        return 'progress'

    @models.permalink
    def get_absolute_url(self):
        return ('article_full', (self.pk, ))

    def get_admin_url(self):
        return get_admin_url(self)

    def __unicode__(self):
        return self.title

    def save(self, force_insert=False, force_update=False, using=None):
        ru_names = {
            'published': u'опубликована',
            'approved': u'в выпуске',
            'done': u'готова',
            'progress': u'в процессе'
        }
        if self.pk:
            self.status_db = ru_names.get(self.status(), '?')
        else:
            self.status_db = ru_names.get('progress', '?')
        return super(Article, self).save(force_insert, force_update, using)

    class Meta:
        verbose_name = u'cтатья'
        verbose_name_plural = u'cтатьи'
        ordering = ('-created_dt', )
示例#7
0
class TradingAccount(models.Model):
    """
    User's account abstract from trading engine intrinsics.
    """
    user = models.ForeignKey(User, related_name="accounts")
    # TODO: Rename id later
    mt4_id = models.IntegerField(
        _("Account id"),
        db_column="mt4_id",
        help_text=_("Account ID at the trading platform"),
        db_index=True)
    invoice_amount = models.PositiveIntegerField(_("Invoice amount"),
                                                 default=1)
    group_name = models.CharField(_("Account type"),
                                  max_length=50,
                                  blank=True,
                                  null=True,
                                  db_column="_group")
    creation_ts = models.DateTimeField(_("Creation timestamp"),
                                       default=datetime.now)
    is_deleted = models.BooleanField(_("Account deleted"), default=False)
    is_archived = models.BooleanField(
        _("Account is archived"),
        default=False,
        help_text=_("Account was automatically archived"))
    deleted_comment = models.TextField(_("Deletion reason"),
                                       null=True,
                                       blank=True)
    client_agreement = models.FileField(
        upload_to=upload_to('client_agreements'), null=True, blank=True)
    partner_agreement = models.FileField(
        upload_to=upload_to('partner_agreements'), null=True, blank=True)
    registered_from_partner_domain = models.ForeignKey(
        'referral.PartnerDomain',
        blank=True,
        null=True,
        editable=False,
        on_delete=models.SET_NULL)

    is_fully_withdrawn = models.BooleanField(_("Fully withdrawn"),
                                             editable=False,
                                             default=False)

    REASON_CHARGEBACK = 'Needs deposit verification'
    REASON_BAD_DOCUMENT = 'Doc. verification failed'

    REASONS_CHOICES = {
        REASON_CHARGEBACK: REASON_CHARGEBACK,
        REASON_BAD_DOCUMENT: REASON_BAD_DOCUMENT,
    }
    last_block_reason = models.CharField(_("Last block reason"),
                                         null=True,
                                         blank=True,
                                         max_length=200,
                                         choices=REASONS_CHOICES.items())

    previous_agent_account = models.PositiveIntegerField(
        null=True,
        blank=True,
        editable=False,
        help_text="Used to return to previous IB",
    )
    is_agreed_managed = models.BooleanField(_("Investment agreement accepted"),
                                            editable=True,
                                            default=False)

    qualified_for_own_reward = models.BooleanField(
        default=False,
        editable=False,
        help_text=
        u"Only for IBs. During the last verification, this partner could receive IB commission for own accs"
    )
    agreement_type = models.CharField(u"Agreement type",
                                      max_length=70,
                                      choices=TYPE_OF_AGREEMENT,
                                      default=None,
                                      null=True,
                                      blank=True)

    # Added in UpTrader to decouple from MT4
    platform_type = models.CharField(u"Trading platform type",
                                     max_length=70,
                                     choices=TYPE_OF_PLATFORM,
                                     default="mt4",
                                     null=False,
                                     blank=False)

    # ugly hack - Leverage stored here because of CFH
    _leverage = models.IntegerField(_("Account leverage"), default=50)
    # and login for CFH
    _login = models.CharField(_("Login"),
                              default=None,
                              null=True,
                              max_length=200)

    objects = TradingAccountManager()

    class Meta:
        ordering = ["mt4_id", "group_name"]
        unique_together = [("user", "mt4_id")]

    # @property
    # def currency(self):
    #     # type: () -> Currency
    #     """
    #     Currency of this account.
    #     """
    #     cur = get_by_group(self.group_name)
    #     return cur or USD
    currency = USD

    @property
    def trades(self):
        # type: () -> queryset_like
        """
        All trades made from this account.
        """
        return self.api.account_trades(self)

    @property
    def open_trades(self):
        # type: () -> queryset_like
        """
        Still active trades/positions.
        """
        from platforms.mt4 import NEVER
        return self.trades.filter(close_time=NEVER)

    @property
    def deferred_trades(self):
        # type: () -> queryset_like
        """
        Trades with delayed execution.
        """
        return self.api.account_deferred_trades(self)

    @property
    def closed_trades(self):
        # type: () -> queryset_like
        """
        Trades/Positions that already closed.
        """
        from platforms.mt4 import NEVER
        return self.trades.exclude(close_time=NEVER)

    def get_available_leverages(self):
        # type: () -> List[int]
        """
        Return available to this account leverages.
        """
        if not self.group:
            return []

        return self.api.account_available_leverages(self)

    @cached_property
    def is_demo(self):
        # type: () -> bool
        """Returns True if account is a demo and False otherwise."""
        log.debug("demo_regex=%s" % demo_regex())
        return bool(re.compile(demo_regex()).match(self.group_name or ""))

    @cached_property
    def is_ib(self):
        # type: () -> bool
        # Should be left as-is, and then refactored into new IB system
        """Returns True if account is an IB (partner) account and False otherwise."""
        return self.group_name and 'ARM_MT4_Agents' in self.group_name

    @cached_property
    def is_micro(self):
        # type: () -> bool
        """
        $1.00 on Micro account is $0.01 in real life
        1 lot on Micro account is 0.01 real lot etc.
        """
        return bool(re.compile(micro_regex()).match(self.group_name or ""))

    def __unicode__(self):
        # type: () -> unicode
        """
        Unicode representstion.
        """
        if self.group:
            return u"%s (%s)" % (self.mt4_id, self.group)
        else:
            return unicode(self.mt4_id)

    @cached_property
    def group(self):
        # type: () ->  str
        """
        Account type (despite naming).
        Returns AccountType object, not string!
        """
        from platforms.cfh.exceptions import CFHError
        from platforms.strategy_store.exceptions import SSError
        try:
            return get_account_type(self.api.account_group(self))
        except (CFHError, SSError):
            return None

    @cached_property
    def api(self):
        # type: () -> object
        """
        Get module facade with platform-specific functionality.
        """
        log.debug("platform_type=%s" % self.platform_type)
        if self.platform_type == "mt4":
            from mt4 import ApiFacade
            return ApiFacade()
        # Other platforms
        elif self.platform_type == "strategy_store":
            from strategy_store import ApiFacade  # type: ignore
            return ApiFacade(settings.SS_API_HOST, settings.SS_API_LOGIN,
                             settings.SS_API_TOKEN)
        elif self.platform_type == "cfh":
            from cfh import ApiFacade  # type: ignore
            # TODO: store servers inside accounts, because it may be random in general
            if self.is_demo:
                broker_api = settings.DEMO_CFH_API_BROKER
                clientadmin_api = settings.DEMO_CFH_API_CLIENTADMIN
                clientadmin_api_login = settings.DEMO_CFH_API_LOGIN
                clientadmin_api_passwd = settings.DEMO_CFH_API_PASSWORD
                broker_api_login = settings.DEMO_CFH_API_LOGIN
                broker_api_passwd = settings.DEMO_CFH_API_PASSWORD
            else:
                broker_api = settings.CFH_API_BROKER
                clientadmin_api = settings.CFH_API_CLIENTADMIN
                clientadmin_api_login = settings.CFH_API_LOGIN
                clientadmin_api_passwd = settings.CFH_API_PASSWORD
                broker_api_login = settings.CFH_API_LOGIN
                broker_api_passwd = settings.CFH_API_PASSWORD

            return ApiFacade(broker_api, clientadmin_api,
                             clientadmin_api_login, clientadmin_api_passwd,
                             broker_api_login, broker_api_passwd)

    def save(self, **kwargs):
        # type: (**object) -> None
        """
        Delegate model saving to platform.
        """
        log.info("Saving account %d" % self.mt4_id)
        self.api.account_update(self)
        super(TradingAccount, self).save(**kwargs)

    @property
    def leverage(self):
        # type: () -> int
        """
        Credit leverage of an account.
        """
        from platforms.cfh.exceptions import CFHError
        from platforms.strategy_store.exceptions import SSError
        try:
            return self.api.account_leverage(self)
        except (CFHError, SSError):
            return '---'

    @property
    def is_disabled(self):
        # type: () -> bool
        """
        Is account disabled?
        """
        return self.api.account_disabled(self)

    @property
    def balance_money(self):
        # type: () -> Money
        """
        Return account balance in account currency.
        Returns Money object.
        """
        return self.get_balance_money()

    @property
    def referral_money(self):
        return self.get_referral_money()

    @property
    def equity_money(self):
        # type: () -> Money
        """
        Return account equity (value of open positions + balance)
        Returns Money object.
        """
        try:
            return Money(self.api.account_equity(self), self.currency)
        except PlatformError as e:
            return NoneMoney()

    def check_connect(self):
        log.debug("Checking account connection for {}".format(self.mt4_id))
        return self.api.account_check_connect(self)

    def check_password(self, password):
        # type: (str) -> bool
        """
        Check account password (platform-specific).
        Returns boolean.
        """
        log.info("Changing password for account %d" % self.mt4_id)
        return self.api.account_check_password(self, password)

    def change_balance(self, amount, comment, **kwargs):
        # type: (float, unicode, **object) -> float
        """
        Change account balance by some amount (positive or negative) with comment.
        request_id & transaction_type not used for now.
        Returns changed amount (positive!), None in case of errors.
        """
        log.info("Changing balance on account %d by %.2f%s" %
                 (self.mt4_id, amount, self.currency.symbol))
        if amount > 0:
            return self.api.account_deposit(self,
                                            abs(amount),
                                            comment=comment,
                                            **kwargs)
        else:
            return self.api.account_withdraw(self,
                                             abs(amount),
                                             comment=comment,
                                             **kwargs)

    @property
    def agent_clients(self):
        # type: () -> List[User]
        """
        Clients, introduced by agent.
        :return: list of User objects.
        """
        return User.objects.filter(profile__agent_code=self.mt4_id)

    @property
    def agent_accounts(self):
        """
        Their accounts.
        :return: list of TradingAccounts.
        """
        return TradingAccount.objects.filter(user__in=self.agent_clients)

    def open_orders_count(self):
        # type: () -> int
        """A shortcut for open orders count"""
        return len(self.open_trades)

    def get_history(self,
                    start=None,
                    end=None,
                    opened=False,
                    count_limit=None):
        # type: (datetime, datetime, bool, int) -> queryset_like
        """
        History of trades on account from start date to end date, which are opened or not with count_limit.
        Returns list of trades, None on errors.
        """
        log.debug("Getting history on account %d" % self.mt4_id)
        trades = self.api.account_trades(self,
                                         from_date=start
                                         or (datetime.now() - timedelta(300)),
                                         to_date=end or datetime.now())
        if opened:
            trades = trades.filter(close_time__isnull=True)
        return trades[:count_limit or sys.maxint]

    @cached_property
    def no_inout(self):
        # type: () -> bool
        """
        Determines if deposit/withdrawal operations are blocked for this account type.
        Returns boolean.
        """
        return getattr(self.group, 'no_inout', True)

    @property
    def has_restore_issue(self):
        # type: () -> bool
        """
        User has requested to restore this account from archive.
        Returns boolean.
        """
        # A shortcut for templates
        from issuetracker.models import RestoreFromArchiveIssue  # Circular import

        if not self.is_archived:
            return False
        return RestoreFromArchiveIssue.objects.filter(account=self,
                                                      status="open").exists()

    def change_leverage(self, leverage_value):
        # type: (int) -> bool
        """
        Change leverage value of account in platform-specific way.
        Returns changed value, None on errors.
        """
        log.info("Changing leverage on account %d to %d", self.mt4_id,
                 leverage_value)
        self._leverage = leverage_value
        return self.api.account_change_leverage(self, leverage_value)

    def block(self, value=True, block_reason=None):
        # type: (bool, unicode) -> None
        if value:
            log.info("Blocking account %d", self.mt4_id)
        else:
            log.info("UnBlocking account %d", self.mt4_id)
        if block_reason:
            log.info("Reason: %s" % block_reason)

        if value and block_reason not in self.REASONS_CHOICES:
            raise ValueError(u'Reason %s not in REASONS_CHOICES' %
                             block_reason)

        if value:
            self.api.account_block(self)
            if block_reason == TradingAccount.REASON_CHARGEBACK:
                from issuetracker.models import CheckOnChargebackIssue
                if not CheckOnChargebackIssue.objects.filter(
                        author=self.user,
                        status__in=("open", "processing")).exists():
                    issue = CheckOnChargebackIssue(author=self.user)
                    issue.save()
            Event.ACCOUNT_BLOCK.log(self, {"reason": block_reason})
            self.last_block_reason = block_reason
        else:
            self.api.account_unblock(self)
            Event.ACCOUNT_UNBLOCK.log(self)
            self.last_block_reason = None

        self.save()

    def change_password(self, password=None):
        # type: (str) -> str
        """
        Change password of account in platform-specific way.
        """
        log.info("Changing password for account %d" % self.mt4_id)
        if not password:
            from platforms.utils import create_password
            password = create_password()
        return self.api.account_change_password(self, password)

    def get_balance(self, currency=None, with_bonus=False):
        # type: (Currency, bool) -> Tuple[float, Currency]
        from platforms.converter import convert_currency
        balance = normalize(self, self.api.account_balance(self))

        #if we should return value as it is, return with original currency
        if not currency:
            return balance, self.currency

        return convert_currency(balance, self.currency, currency)

    def get_balance_money(self, with_bonus=False):
        from platforms.cfh.exceptions import CFHError
        from platforms.strategy_store.exceptions import SSError
        # type: (bool) -> Money
        try:
            return Money(*self.get_balance(with_bonus=with_bonus))
        except (CFHError, SSError):
            return NoneMoney()

    def get_referral_money(self):
        return Money(sum(map(lambda r: float(r.api.account_balance(r) or 0), self.agent_accounts.non_demo_active())) \
            if self.is_ib else 0, USD)

    def get_totals(self):
        from payments.models import DepositRequest, WithdrawRequest
        return {
            'deposit':
            sum(
                map(
                    lambda r: r['amount'],
                    DepositRequest.objects.filter(
                        account=self, is_committed=True).values('amount'))),
            'withdraw':
            sum(
                map(
                    lambda r: r['amount'],
                    WithdrawRequest.objects.filter(
                        account=self, is_committed=True).values('amount'))),
        }
示例#8
0
class UserProfile(StateSavingModel):
    NET_CAPITAL_CHOICES = ANNUAL_INCOME_CHOICES = (
        ("> 100000", _("over 100 000")),
        ("50000 - 100000", "50 000 - 100 000"),
        ("10000 - 50000", "10 000 - 50 000"),
        ("< 10000", _("below 10 000")),
    )
    FINANCIAL_COMMITMENTS = (
        ("< 20%", _("Less than 20%")),
        ("21% - 40%", "21% - 40%"),
        ("41% - 60%", "41% - 60%"),
        ("61% - 80%", "61% - 80%"),
        ("> 80%", _("Over 80%")),
    )

    FR_TRANSACTIONS = (
        ("Daily", _("Daily")),
        ("Weekly", _("Weekly")),
        ("Monthly", _("Monthly")),
        ("Yearly", _("Yearly")),
    )

    AV_TRANSACTIONS = (
        ("< 10000", _("Less than 10 000")),
        ("10000 - 50000", "10 000 - 50 000"),
        ("50001 - 100000", "50 001 - 100 000"),
        ("100001 - 200000", "100 001 - 200 000"),
        ("> 250000", _("Over 250 000")),
    )

    EMPLOYMENT_STATUS = (("Employed", _("Employed")),
                         ("Unemployed", _("Unemployed")), ("Self employed",
                                                           _("Self employed")),
                         ("Retired", _("Retired")), ('Student', _('Student')))

    PURPOSES = (("Investment", _("Investment")), ("Hedging", _("Hedging")),
                ("Speculative trading", _("Speculative trading")))

    user = models.OneToOneField(User, unique=True, related_name="profile")
    middle_name = models.CharField(_('Middle name'),
                                   max_length=45,
                                   blank=True,
                                   null=True)
    birthday = models.DateField(_('Birthday'), blank=True, null=True)
    country = models.ForeignKey('geobase.Country',
                                verbose_name=_('Country'),
                                blank=True,
                                null=True,
                                help_text=_('Example: Russia'))
    city = models.CharField(_('City'),
                            max_length=100,
                            blank=True,
                            null=True,
                            help_text=_('The city where you live'))
    state = models.ForeignKey('geobase.Region',
                              verbose_name=_('State / Province'),
                              blank=True,
                              null=True)
    address = models.CharField(_('Residential address'),
                               max_length=80,
                               blank=True,
                               null=True,
                               help_text=_('Example: pr. Stachek, 8A'))
    skype = models.CharField(_('Skype'),
                             max_length=80,
                             blank=True,
                             null=True,
                             help_text=_('Example: gc_clients'))
    icq = models.CharField(_('ICQ'),
                           blank=True,
                           null=True,
                           max_length=20,
                           help_text=_('Example: 629301132'))
    phone_home = CountryPhoneCodeField(_('Home phone'),
                                       max_length=40,
                                       db_index=True,
                                       blank=True,
                                       null=True,
                                       help_text=_('Example: 8-800-333-1003'))
    phone_work = CountryPhoneCodeField(
        _('Work phone'),
        max_length=40,
        db_index=True,
        blank=True,
        null=True,
        help_text=_('Example: +7 (812) 300-81-96'))
    phone_mobile = CountryPhoneCodeField(
        _('Mobile phone'),
        max_length=40,
        db_index=True,
        blank=True,
        null=True,
        help_text=_('Example: +7 (911) 200-19-55'))
    avatar = models.ImageField(_('Avatar'),
                               blank=True,
                               null=True,
                               upload_to=upload_to('userfiles/avatars'),
                               help_text=_('Your photo'))
    social_security = models.CharField(_('Social security number'),
                                       max_length=50,
                                       blank=True,
                                       null=True)
    tin = models.CharField(_('TIN'),
                           max_length=50,
                           help_text=_('Tax identification number'),
                           blank=True,
                           null=True)
    manager = models.ForeignKey(User,
                                related_name="managed_profiles",
                                blank=True,
                                null=True,
                                verbose_name=u"Менеджер по торговле")
    manager_auto_assigned = models.BooleanField(
        u'Менеджер назначен атоматически', default=True)
    assigned_to_current_manager_at = models.DateTimeField(
        _('Assigned to manager at'), blank=True, null=True)
    language = models.CharField(max_length=20,
                                choices=settings.LANGUAGES,
                                blank=True,
                                null=True)
    agent_code = models.PositiveIntegerField(
        verbose_name=_('Agent code'),
        blank=True,
        null=True,
        help_text=_('If you don\'t know what it is, leave empty'))
    lost_otp = models.BooleanField(_("Did user lose his OTP"), default=False)
    auth_scheme = models.CharField(max_length=10,
                                   verbose_name="Auth scheme",
                                   choices=AUTH_SCHEMES,
                                   null=True)
    params = JSONField(_("Details"), blank=True, null=True, default={})
    subscription = models.ManyToManyField('massmail.CampaignType',
                                          verbose_name=u'Тип рассылки',
                                          blank=True,
                                          null=True)
    user_from = JSONField(_("Source"), blank=True, null=True, default={})
    registered_from = models.CharField(max_length=255,
                                       verbose_name=_('Registered from'),
                                       default="",
                                       blank=True)
    last_activity_ts = models.DateTimeField(_('Last activity'),
                                            default=datetime(1970, 1, 1),
                                            db_index=True)
    last_activities = ArrayField(models.CharField(max_length=255),
                                 verbose_name='Last activities',
                                 default=list)
    nationality = models.ForeignKey('geobase.Country',
                                    verbose_name=_('Nationality'),
                                    related_name='nations',
                                    blank=True,
                                    null=True,
                                    help_text=_('Example: Russia'))
    net_capital = models.CharField(_('Net capital (USD)'),
                                   max_length=50,
                                   choices=NET_CAPITAL_CHOICES,
                                   blank=True,
                                   null=True)
    annual_income = models.CharField(_('Annual income (USD)'),
                                     max_length=50,
                                     choices=ANNUAL_INCOME_CHOICES,
                                     blank=True,
                                     null=True)
    tax_residence = models.ForeignKey('geobase.Country',
                                      verbose_name=_('Tax residence'),
                                      related_name='taxes',
                                      blank=True,
                                      null=True)
    us_citizen = models.BooleanField(_('US citizen'), default=False)
    employment_status = models.CharField(_('Employment status'),
                                         max_length=90,
                                         choices=EMPLOYMENT_STATUS,
                                         blank=True,
                                         null=True)
    source_of_funds = models.CharField(_('Source of funds'),
                                       max_length=90,
                                       blank=True,
                                       null=True)
    nature_of_biz = models.CharField(_('Nature of business'),
                                     max_length=90,
                                     blank=True,
                                     null=True)
    financial_commitments = models.CharField(
        _('Monthly financial commitments'),
        max_length=90,
        choices=FINANCIAL_COMMITMENTS,
        blank=True,
        null=True)

    account_turnover = models.IntegerField(
        _('Approximate volume of investments per annum (USD)'),
        null=True,
        blank=True)
    purpose = models.CharField(_('Purpose to open an Arum capital account'),
                               choices=PURPOSES,
                               max_length=50,
                               blank=True,
                               null=True)

    education_level = models.CharField(_('Level of education'),
                                       max_length=90,
                                       blank=True,
                                       null=True)

    allow_open_invest = models.BooleanField(_('Can open ECN.Invest accounts'),
                                            default=False)

    investment_undertaking = HStoreField(
        default={"Units of collective investment undertaking": "No"})

    transferable_securities = HStoreField(
        default={"Transferable securities": "No"})

    derivative_instruments = HStoreField(default={
        "Derivative instruments (incl. options, futures, swaps, FRAs, etc.)":
        "No"
    })

    forex_instruments = HStoreField(
        default={"Trading experience FOREX/CFDs": "No"})

    is_partner = models.BooleanField(
        _("Is partner"),
        help_text=_("User can browse partner section"),
        default=False)

    objects = UserProfileManager()

    email_verified = models.BooleanField(_('Email is verified'), default=False)

    class Meta:
        verbose_name = _('User profile')
        verbose_name_plural = _('User profiles')

    def __unicode__(self):
        if self.user.first_name and self.user.last_name:
            return u"%s %s" % (self.user.first_name, self.user.last_name)
        else:
            return unicode(self.user)

    ###
    # Helpers section
    ###

    def add_last_activity(self, name, check=None, ts=None):
        UserProfile.objects.filter(pk=self.pk).add_last_activity(name=name,
                                                                 check=check,
                                                                 ts=ts)
        self.refresh_from_db(fields=['last_activities', 'last_activity_ts'])

    @property
    def last_activity_translated(self):
        from django.utils.translation import ugettext_lazy as _
        return map(_, self.last_activities)

    @property
    def trades(self):
        return AbstractTrade.objects.filter(login__in=list(
            self.user.accounts.values_list("mt4_id", flat=True)))

    @property
    def is_russian(self):
        return (self.country is None) or self.country.is_russian_language

    def has_groups(self, *groups):
        """
        Returns True if related User is a member of at least one of the
        given groups, else returns False.
        """
        return self.user.groups.filter(name__in=groups).exists()

    has_group = has_groups  # Would work perfectly fine for one group.

    def update_subscription(self):
        # Common massmail campaigns
        from massmail.models import Unsubscribed, CampaignType

        if not self.subscription.all():
            Unsubscribed.objects.get_or_create(email=self.user.email)
        else:
            try:
                obj = Unsubscribed.objects.get(email=self.user.email)
            except Unsubscribed.DoesNotExist:
                pass
            else:
                obj.delete()

    ###
    # Validations section
    ###
    INCOMPLETE = 0
    NO_DOCUMENTS = 1
    UNVERIFIED = 2
    VERIFIED = 3

    STATUSES = {
        INCOMPLETE: _('Registration incomplete'),
        NO_DOCUMENTS: _('Documents have not been uploaded'),
        UNVERIFIED: _('Pending approval'),
        VERIFIED: _('Approved'),
    }

    @property
    def status(self):
        '''
        Returns user status from STATUSES:
        VERIFIED if all satisfied:
            a) profile form is complete (all required fields not null && verified email)
            b) there is no rejected docs (is_rejected in UserDocument)
            c) and user is marked validated (first_name & last_name in user)
        UNVERIFIED if all satisfied:
            a) profile is complete
            b) there are uploaded but not rejected docs
        NO_DOCUMENTS if all satisfied:
            a) profile is complete
            b) there are no uploaded docs
        INCOMPLETE if:
            profile is not complete OR there are rejected docs!
        '''
        required_fields = [
            'birthday', 'nationality', 'state', 'city', 'address',
            'net_capital', 'annual_income'
        ]
        complete_profile = all(
            getattr(self, f) for f in required_fields) and self.email_verified

        user_documents = UserDocument.objects.filter(
            user=self.user).values_list('is_rejected', flat=True)

        if user_documents and all(user_documents):
            return self.INCOMPLETE
        if complete_profile:
            if self.has_valid_documents():
                return self.VERIFIED
            if self.has_documents():
                return self.UNVERIFIED
            return self.NO_DOCUMENTS
        else:
            return self.INCOMPLETE

    def has_valid_phone(self):
        return self.has_validation("phone_mobile")

    def make_valid(self, name):
        set_validation(self.user, name, True)

    def drop_valid(self, name):
        set_validation(self.user, name, False)

    def has_documents(self, *document_types):

        if not document_types:
            document_types = (DOCUMENT_TYPES.PASSPORT_SCAN,
                              DOCUMENT_TYPES.RESIDENTIAL_ADDRESS,
                              DOCUMENT_TYPES.ADDRESS_PROOF)

        return UserDocument.objects.filter(user=self.user,
                                           name__in=document_types,
                                           is_deleted=False,
                                           is_rejected=False).exists()

    def has_valid_documents(self):
        return self.has_validation("first_name", "last_name")

    def make_documents_valid(self):
        '''
        '''
        self.make_valid('first_name')
        self.make_valid('last_name')
        # # Make all documents sent by user not rejected
        # UserDocument.objects.filter(user=self.user).update(is_rejected=False)

    def make_documents_invalid(self, *document_types):
        '''
        '''
        if not document_types:
            document_types = [
                DOCUMENT_TYPES.PASSPORT_SCAN,
                DOCUMENT_TYPES.RESIDENTIAL_ADDRESS,
                DOCUMENT_TYPES.ADDRESS_PROOF
            ]

        UserDocument.objects.filter(user=self.user,
                                    name__in=document_types,
                                    is_deleted=False,
                                    is_rejected=False).update(is_rejected=True)

        self.drop_valid('first_name')
        self.drop_valid('last_name')

    def has_validation(self, *fields):

        if not fields:
            return False

        return self.user.validations.filter(
            key__in=fields, is_valid=True).count() == len(fields)

    ###
    # OTP section
    ###

    @property
    def otp_devices(self):
        for device_type in DEVICE_TYPES:
            devices = device_type.objects.devices_for_user(
                self.user).filter(is_deleted=False)
            if devices:
                return devices
        return devices

    @property
    def otp_device(self):
        if self.otp_devices:
            return self.otp_devices[0]

    @property
    def has_otp_devices(self):
        return any(
            imap(
                lambda x: x.objects.devices_for_user(self.user).filter(
                    is_deleted=False).exists(), DEVICE_TYPES))

    def delete_otp_devices(self, lost_otp=False):
        self.otp_devices.update(is_deleted=True)
        self.auth_scheme = None
        if lost_otp:
            self.lost_otp = True
        self.save()

    def set_manager(self, manager, taken_by_manager=False, similar=True):
        """
        Sets manager to this profile and similars with flags and dates
        """
        if manager != self.manager:
            self.assigned_to_current_manager_at = datetime.now()
            self.manager = manager
        self.save()
        if similar:
            with Logger.with_tag('set_manager_similar'):
                with Logger.with_params(set_manager_similar_to=self.pk):
                    for sim in self.similar:
                        sim.set_manager(self.manager,
                                        taken_by_manager=taken_by_manager,
                                        similar=False)

    def autoassign_manager(self, force=False, with_similar=True):
        manager = User.objects.filter(is_superuser=True).first()

        if manager:
            self.set_manager(manager, similar=with_similar)
        return manager

    def get_manager_slices(self, start, end):
        if isinstance(start, date):
            start = datetime.combine(start, datetime.min.time())
        if isinstance(end, date):
            end = datetime.combine(end, datetime.min.time())
        changes = Logger.objects.filter(event=Events.MANAGER_CHANGED,
                                        at__range=(start, end),
                                        object_id=self.user.pk).order_by('at')
        if changes:
            old_manager_id = changes[0].params.get('old_id')
            starting_manager = User.objects.filter(
                id=old_manager_id).first() if old_manager_id else None
        else:
            starting_manager = self.manager
        result = [(start, starting_manager)]
        for change in changes:
            changed_to_id = change.params.get('new_id')
            if changed_to_id:
                changed_to = User.objects.filter(id=changed_to_id).first()
                if changed_to:
                    result.append((change.at, changed_to))
        return result

    @staticmethod
    def get_manager_from_slices(slices, date):
        if date < slices[0][0]:
            return slices[0][1]
        for slice in reversed(slices):
            if slice[0] < date:
                return slice[1]
        return slices[-1][1]

    def get_manager_at(self, at):
        """At the time of writing, our managers work for 1-2 weeks and leave, so this method is really indispensable"""
        last_change = Logger.objects.filter(
            event=Events.MANAGER_CHANGED, at__lte=at,
            object_id=self.user.pk).order_by('-at').first()
        if last_change:
            changed_to_id = last_change.params.get('new_id')
            if changed_to_id:
                changed_to = User.objects.filter(pk=changed_to_id).first()
                if changed_to:
                    return changed_to
        return self.manager  # Ok, we couldn't find the historic manager

    def get_amo(self):
        """Returns AmoContact with protection from non existing"""
        return None

    def get_full_name(self):
        name_parts = filter(lambda x: x, [
            self.user.last_name,
            self.user.first_name,
            self.middle_name,
        ])
        if name_parts:
            return ' '.join(name_parts)
        return self.user.username

    def get_short_name(self):
        return ' '.join([
            self.user.last_name or self.user.username,
            self.user.first_name[0] + '.' if self.user.first_name else '',
            self.middle_name[0] + '.' if self.middle_name else '',
        ]).strip()

    @property
    def related_logs(self):
        qs = Logger.objects.by_object(self.user)
        qs |= Logger.objects.by_object(self)
        qs |= Logger.objects.by_user(self.user)

        from platforms.models import TradingAccount
        pma_ids = self.user.accounts.values_list('id', flat=True)
        qs |= Logger.objects.by_object_ids(TradingAccount, pma_ids)
        return qs

    @property
    def social_profile_link(self):
        social_auth = self.user.social_auth.first()
        if social_auth is None:
            return
        if social_auth.provider == "facebook":
            return u"https://www.facebook.com/%s" % social_auth.uid
        elif social_auth.provider == "vk-oauth2":
            return u"https://vk.com/id%s" % social_auth.uid
        elif social_auth.provider == "odnoklassniki-oauth2":
            return u"http://ok.ru/profile/%s" % social_auth.uid

    ###
    # Alternative sites section
    ###

    def get_site_name(self):
        if self.registered_from in settings.PRIVATE_OFFICES:
            return settings.PRIVATE_OFFICES[self.registered_from]['site_name']
        else:
            return "Arum Capital"

    def get_site_domain(self):
        if self.registered_from in settings.PRIVATE_OFFICES:
            return settings.PRIVATE_OFFICES[self.registered_from]['domain']
        else:
            return "arumcapital.eu"

    def get_time_zone(self):
        time_zone = None
        if self.state:
            time_zone = self.state.get_time_zone()
        if not time_zone and self.country:
            time_zone = self.country.get_time_zone()
        return time_zone

    def get_local_time(self):
        time_zone = self.get_time_zone()
        if time_zone is None:
            return
        return pytz.timezone("Europe/Moscow").localize(
            datetime.now()).astimezone(time_zone)

    @property
    def deposit_requests(self):
        return DepositRequest.objects.filter(account__user=self.user)

    def add_verified_card(self, card_number):
        self.params.setdefault('verified_cards', []).append(card_number)
        self.save()

    def is_card_verified(self, card_number):
        return card_number in self.params.get('verified_cards', [])

    def push_notification(self, text, extra=None):
        return

    def full_name(self):
        return format_html(
            '<span style="color: #011d37; '
            'font-size: 38px; '
            'font-family: Arial, Helvetica, sans-serif; '
            'text-shadow: 0px 0px 6px rgba(1,1,1,0.4);">'
            '{} {} {}</span>',
            self.user.first_name,
            self.user.last_name,
            self.middle_name if self.middle_name else '',
        )