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])
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")
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>")
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")
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", <img src="cid:testimage" />') def __unicode__(self): return u'%s: %s' % (self.template, self.content_id)
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', )
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'))), }
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 '', )