def obj_currency(self): currency = CleanText( '//th[contains(text(), "Montant total")]/small')(self) if currency: return Currency().filter(currency) return Currency().filter( CleanText('//table[@class="fiche"]//td/small')(self))
class item(ItemElement): klass = Loan def condition(self): return 'Billet financier' not in CleanText('./td[1]')(self) obj_id = CleanText('./td[2]') obj_number = Field('id') obj_label = CleanText('./td[1]') obj_type = Map(Field('label'), ACCOUNT_TYPES, Account.TYPE_LOAN) obj_next_payment_amount = Env('next_payment_amount') obj_total_amount = Env('total_amount') obj_currency = Currency('./td[@class="cel-devise"]') obj_url = Link('./td[2]/a', default=None) obj_iban = None obj__form = None def obj_balance(self): balance = Env('balance')(self) return -abs(balance) def parse(self, obj): # We must handle Loan tables with 5 or 6 columns if CleanText('//tr[contains(@class, "colcelligne")][count(td) = 5]')(self): # History table with 4 columns (no loan details) self.env['next_payment_amount'] = NotAvailable self.env['total_amount'] = NotAvailable self.env['balance'] = CleanDecimal.French('./td[4]//*[@class="montant3" or @class="montant4"]', default=NotAvailable)(self) elif CleanText('//tr[contains(@class, "colcelligne")][count(td) = 6]')(self): # History table with 5 columns (contains next_payment_amount & total_amount) self.env['next_payment_amount'] = CleanDecimal.French('./td[3]//*[@class="montant3"]', default=NotAvailable)(self) self.env['total_amount'] = CleanDecimal.French('./td[4]//*[@class="montant3"]', default=NotAvailable)(self) self.env['balance'] = CleanDecimal.French('./td[5]//*[@class="montant3"]', default=NotAvailable)(self)
class get_unique_card(ItemElement): item_xpath = '//table[@class="ca-table"][@summary]' klass = Account # Transform 'n° 4999 78xx xxxx xx72' into '499978xxxxxxxx72' obj_number = CleanText('//table[@class="ca-table"][@summary]//tr[@class="ligne-impaire"]/td[@class="cel-texte"][1]', replace=[(' ', ''), ('n°', '')]) # Card ID is formatted as '499978xxxxxxxx72MrFirstnameLastname-' obj_id = Format('%s%s', Field('number'), CleanText('//table[@class="ca-table"][@summary]//caption[@class="caption"]//b', replace=[(' ', '')])) # Card label is formatted as 'Carte VISA Premier - Mr M Lastname' obj_label = Format('%s - %s', CleanText('//table[@class="ca-table"][@summary]//tr[@class="ligne-impaire ligne-bleu"]/th[@id="compte-1"]'), CleanText('//table[@class="ca-table"][@summary]//caption[@class="caption"]//b')) obj_balance = CleanDecimal(0) obj_coming = CleanDecimal.French('//table[@class="ca-table"][@summary]//tr[@class="ligne-paire"]//td[@class="cel-num"]', default=0) obj_currency = Currency(Regexp(CleanText('//th[contains(text(), "Montant en")]'), r'^Montant en (.*)')) obj_type = Account.TYPE_CARD obj__form = None
class item(ItemElement): klass = Account def condition(self): # Skip card coming lines return 'Encours carte' not in CleanText(TableCell('label', colspan=True))(self) obj_id = CleanText(TableCell('id', colspan=True)) obj_number = Field('id') obj_label = CleanText(TableCell('label', colspan=True)) obj_type = Map(Field('label'), ACCOUNT_TYPES, Account.TYPE_UNKNOWN) obj_currency = Currency(TableCell('currency', colspan=True)) obj_url = None # Accounts may have an 'Operations' balance or a 'Value' balance def obj_balance(self): value_balance = CleanText(TableCell('value_balance', default='', colspan=True))(self) # Skip invalid balance values in the 'Value' column (for example for Revolving credits) if value_balance not in ('', 'Montant disponible'): return CleanDecimal.French().filter(value_balance) return CleanDecimal.French(CleanText(TableCell('operation_balance', default='', colspan=True)))(self) def obj__form(self): # Account forms look like 'javascript:fwkPUAvancerForm('Releves','frm1')' # From this we extract the name (frm1) and fetch the form name on the page. script = Link('.//a', default='')(TableCell('id', colspan=True)(self)[0]) if 'javascript' in script: form_search = re.search(r'frm\d+', script) if form_search: account_form = self.page.get_form(name=form_search.group(0)) return self.page.fill_form(account_form, card=False) return None
class fill_account(ItemElement): obj_balance = CleanDecimal.French( '//ul[has-class("m-data-group")]//strong') obj_currency = Currency('//ul[has-class("m-data-group")]//strong') obj_valuation_diff = CleanDecimal.French( '//h3[contains(., "value latente")]/following-sibling::p[1]', default=NotAvailable)
class item(ItemElement): klass = Account TYPES = { 'assurance vie': Account.TYPE_LIFE_INSURANCE, 'perp': Account.TYPE_PERP, 'epargne retraite agipi pair': Account.TYPE_PERP, 'epargne retraite agipi far': Account.TYPE_MADELIN, 'epargne retraite ma retraite': Account.TYPE_PER, 'novial avenir': Account.TYPE_MADELIN, 'epargne retraite novial': Account.TYPE_LIFE_INSURANCE, } obj_id = Regexp(CleanText('.//span[has-class("small-title")]'), r'([\d/]+)') obj_number = obj_id obj_label = CleanText('.//h3[has-class("card-title")]') obj_balance = CleanDecimal.French('.//p[has-class("amount-card")]') obj_valuation_diff = CleanDecimal.French( './/p[@class="performance"]', default=NotAvailable) obj_currency = Currency('.//p[has-class("amount-card")]') obj__acctype = "investment" obj_type = MapIn(Lower(Field('label')), TYPES, Account.TYPE_UNKNOWN) obj_url = Attr('.', 'data-module-open-link--link') obj_ownership = AccountOwnership.OWNER
class get_housing(ItemElement): klass = Housing obj_id = Env('_id') obj_title = CleanText('//h1[@itemprop="name"]') obj_location = CleanText('//span[@class="informations-localisation"]') obj_cost = CleanDecimal('//span[@itemprop="price"]') obj_currency = Currency('//span[@itemprop="price"]') obj_text = CleanHTML('//div[@itemprop="description"]') obj_url = BrowserURL('housing', _id=Env('_id')) obj_area = CleanDecimal(Regexp(CleanText('//h1[@itemprop="name"]'), '(.*?)(\d*) m2(.*?)', '\\2'), default=NotAvailable) obj_price_per_meter = PricePerMeterFilter() def obj_photos(self): photos = [] for img in XPath('//a[@class="thumbnail-link"]/img[@itemprop="image"]')(self): url = Regexp(CleanText('./@src'), 'http://thbr\.figarocms\.net.*(http://.*)')(img) photos.append(HousingPhoto(url)) return photos def obj_details(self): details = dict() for item in XPath('//div[@class="features clearfix"]/ul/li')(self): key = CleanText('./span[@class="name"]')(item) value = CleanText('./span[@class="value"]')(item) if value and key: details[key] = value key = CleanText('//div[@class="title-dpe clearfix"]')(self) value = CleanText('//div[@class="energy-consumption"]')(self) if value and key: details[key] = value return details
class item(ItemElement): klass = Bill obj_id = Format('facture-%s-%s-%s#%s', Slugify(CleanText(TableCell('date'))), Slugify(CleanText(TableCell('amount'))), Slugify(CleanText(TableCell('type'))), Env('sub_id')) obj_url = AbsoluteLink('./td[5]//a', default=NotAvailable) obj_date = Date(CleanText(TableCell('date')), dayfirst=True) obj_label = Format('%s %s %s', CleanText(TableCell('type')), CleanText(TableCell('amount')), CleanText(TableCell('date'))) obj_type = DocumentTypes.BILL obj_price = CleanDecimal(TableCell('amount'), replace_dots=True) obj_currency = Currency(TableCell('amount')) obj_duedate = Date(Regexp(CleanText(TableCell('status')), r'le (\d+)/(\d+)/(\d+)', r'\1/\2/\3'), dayfirst=True) def obj_format(self): if self.obj_url(self): return 'pdf' return NotAvailable def obj_income(self): if self.obj_price(self) < 0: return True return False
class Item(ItemElement): TYPES = { 'CIFO': Account.TYPE_MARKET, 'PEA': Account.TYPE_PEA, 'Excelis VIE': Account.TYPE_LIFE_INSURANCE, 'Satinium': Account.TYPE_LIFE_INSURANCE, 'Satinium CAPI': Account.TYPE_LIFE_INSURANCE, 'Excelis CAPI': Account.TYPE_LIFE_INSURANCE, } klass = Account obj_id = CleanText(TableCell('id')) obj_label = Format('%s %s', CleanText(TableCell('type')), CleanText(TableCell('name'))) obj_currency = Currency(TableCell('valorisation')) obj_bank_name = u'La Banque postale' obj_balance = CleanDecimal(TableCell('valorisation'), replace_dots=True) obj_url = Link(TableCell('id')) obj_iban = NotAvailable def obj_url(self): td = TableCell('id')(self)[0] return Link(td.xpath('./a'))(self) def obj_type(self): return self.TYPES.get( CleanText(TableCell('type'))(self), Account.TYPE_UNKNOWN)
class item(ItemElement): klass = Account def obj_id(self): _id = CleanText('./td[1]')(self) _id = ''.join(i for i in _id if i.isdigit()) return _id obj_number = obj_id obj_label = CleanText('./td[2]', replace=[(' o ', ' ')]) obj__login = CleanDecimal('./td[1]') obj_currency = Currency('./td[6]') obj_company_name = CleanText('./td[3]') def obj__sublabel(self): # Use the second part of the label to determine account index # later on InvestmentPage and remove the 'N ' at the beginning sublabel = CleanText('./td[2]', children=False)(self) if sublabel.startswith('N '): sublabel = sublabel[2:] return sublabel def obj_type(self): return MapIn(Field('label'), self.page.ACCOUNT_TYPES, Account.TYPE_UNKNOWN)(self) def obj_balance(self): # This wonderful website randomly displays separators as '.' or ',' # For example, numbers can look like "€12,345.67" or "12 345,67 €" try: return CleanDecimal.French('./td[6]')(self) except NumberFormatError: return CleanDecimal.US('./td[6]')(self)
class item(ItemElement): klass = Bill obj_id = Format('%s_%s', Env('subid'), Dict('id')) obj_url = Dict('url') obj_date = Date(Dict('date')) obj_format = 'pdf' obj_currency = Currency(Dict('currency'), default=NotAvailable) def obj_price(self): price = CleanDecimal(Dict('price'), default=NotAvailable)(self) if price: return price / 100 return NotAvailable def obj_income(self): if Dict('type')(self) == 'purchase': return False else: # type is 'refund' return True def obj_label(self): if Field('income')(self): return Format('Remboursement du %s', Field('date'))(self) else: return Format('Achat du %s', Field('date'))(self)
def get_account_details(self, account_id): balance = CleanDecimal.French( '//a[div[div[span[span[contains(text(), "%s")]]]]]/div[1]/div[2]/span/span' % account_id, default=NotAvailable)(self.doc) currency = Currency( '//a[div[div[span[span[contains(text(), "%s")]]]]]/div[1]/div[2]/span/span' % account_id, default=NotAvailable)(self.doc) label = CleanText( '//a[div[div[span[span[contains(text(), "%s")]]]]]/div[1]/div[1]/span/span' % account_id, default=NotAvailable)(self.doc) url = Link('//a[div[div[span[span[contains(text(), "%s")]]]]]' % account_id, default=None)(self.doc) if url: account_url = 'https://bgpi-gestionprivee.credit-agricole.fr' + url else: account_url = None return balance, currency, label, account_url
class item(ItemElement): klass = Account def condition(self): # Ignore cards that do not have a coming return CleanText('.//tr[1]/td[@class="cel-num"]')(self) # Transform 'n° 4999 78xx xxxx xx72' into '499978xxxxxxxx72' obj_number = CleanText('.//caption/span[@class="tdb-cartes-num"]', replace=[(' ', ''), ('n°', '')]) # The raw number is used to access multiple cards details obj__raw_number = CleanText( './/caption/span[@class="tdb-cartes-num"]') # Multiple card IDs are formatted as '499978xxxxxxxx72MrFirstnameLastname' obj_id = Format( '%s%s', Field('number'), CleanText('.//caption/span[@class="tdb-cartes-prop"]', replace=[(' ', '')])) # Card label is formatted as 'Carte VISA Premier - Mr M Lastname' obj_label = Format( '%s - %s', CleanText('.//caption/span[has-class("tdb-cartes-carte")]'), CleanText('.//caption/span[has-class("tdb-cartes-prop")]')) obj_type = Account.TYPE_CARD obj_balance = CleanDecimal(0) obj_coming = CleanDecimal.French( './/tr[1]/td[position() = last()]', default=0) obj_currency = Currency( Regexp(CleanText('//span[contains(text(), "Montants en")]'), r'^Montants en (.*)')) obj__form = None
class item(ItemElement): klass = Account obj_id = CleanText('./td[2]') obj_number = Field('id') obj_label = CleanText('./td/span[@class="gras"]') obj_type = Map(Field('label'), ACCOUNT_TYPES, Account.TYPE_UNKNOWN) # Accounts without balance will be skipped later on obj_balance = CleanDecimal.French('./td//*[@class="montant3"]', default=NotAvailable) obj_currency = Currency('./td[@class="cel-devise"]') obj_iban = None obj__form = None def obj_url(self): url = Link('./td[2]/a', default=None)(self) if url and 'BGPI' in url: # This URL is just the BGPI home page, not the account itself. # The real account URL will be set by get_account_details() in BGPISpace. return 'BGPI' return url def validate(self, obj): # Skip 'ESPE INTEG' accounts, these liquidities are already available # on the associated Market account on the Netfinca website return obj.label != 'ESPE INTEG'
class item(ItemElement): klass = Account def condition(self): return len(Dict('accountListInformation')(self)) == 1 def obj_type(self): return self.parent.TYPE_ACCOUNTS.get( Dict('dashboardAccountSubGroupIdentifier')(self)) obj_number = CleanText( Dict('accountListInformation/0/accountNumber')) obj_id = CleanText( Dict('accountListInformation/0/accountNickName')) obj_currency = Currency( Dict('accountListInformation/0/currencyAccountCode')) obj_balance = CleanDecimal( Dict( 'accountGroupMultipleCurrencyInformation/0/accountMarketValueAmount' )) obj_valuation_diff = CleanDecimal(Dict( 'accountGroupMultipleCurrencyInformation/0/profitLossUnrealizedAmount' ), default=NotAvailable)
class item(ItemElement): klass = Account TYPES = { u'assurance vie': Account.TYPE_LIFE_INSURANCE, u'perp': Account.TYPE_PERP, u'epargne retraite agipi pair': Account.TYPE_PERP, u'novial avenir': Account.TYPE_MADELIN, u'epargne retraite novial': Account.TYPE_LIFE_INSURANCE, } condition = lambda self: Field('balance')(self) is not NotAvailable obj_id = Regexp(CleanText('.//span[has-class("small-title")]'), r'([\d/]+)') obj_label = CleanText('.//h3[has-class("card-title")]') obj_balance = CleanDecimal.French('.//p[has-class("amount-card")]') obj_valuation_diff = CleanDecimal.French( './/p[@class="performance"]', default=NotAvailable) def obj_url(self): url = Attr('.', 'data-route')(self) # The Assurance Vie xpath recently changed so we must verify that all # the accounts now have "/savings/" instead of "/assurances-vie/". assert "/savings/" in url return url obj_currency = Currency('.//p[has-class("amount-card")]') obj__acctype = "investment" obj_type = MapIn(Lower(Field('label')), TYPES, Account.TYPE_UNKNOWN)
class item(ItemElement): klass = Account class Type(Filter): def filter(self, label): for pattern, actype in AccountsPage.TYPES.items(): if label.startswith(pattern): return actype return Account.TYPE_UNKNOWN obj__history_url = Link('.//a[1]') obj_id = CleanText('.//span[has-class("numero-compte")]') & Regexp( pattern=r'(\d{3,}[\w]+)', default='') obj_label = CleanText('.//span[has-class("libelle")][1]') obj_currency = Currency('//span[has-class("montant")]') obj_balance = CleanDecimal('.//span[has-class("montant")]', replace_dots=True) obj_type = Type(Field('label')) # Last numbers replaced with XX... or we have to send sms to get RIB. obj_iban = NotAvailable # some accounts may appear on multiple areas, but the area where they come from is indicated obj__owner = CleanText( '(./preceding-sibling::tr[@class="LnMnTiers"])[last()]') def validate(self, obj): if obj.id is None: obj.id = obj.label.replace(' ', '') return True
class item(ItemElement): klass = Housing obj_id = Format( 'colocation-%s', CleanText('./div/header/@id', replace=[('header-offer-', '')])) obj_type = POSTS_TYPES.SHARING obj_advert_type = ADVERT_TYPES.PROFESSIONAL obj_title = CleanText( CleanHTML( './div/header/section/p[@class="property-type"]/span/@title' )) obj_area = CleanDecimal( './div/header/section/p[@class="offer-attributes"]/a/span[@class="offer-area-number"]', default=0) obj_cost = CleanDecimal('./div/header/section/p[@class="price"]', default=0) obj_currency = Currency('./div/header/section/p[@class="price"]') obj_utilities = UTILITIES.UNKNOWN obj_text = CleanText( './div/div[@class="content-offer"]/section[has-class("content-desc")]/p/span[has-class("offer-text")]/@title', default=NotLoaded) obj_date = Date( Regexp( CleanText( './div/header/section/p[has-class("update-date")]'), ".*(\d{2}/\d{2}/\d{4}).*")) obj_location = CleanText( '(./div/div[@class="content-offer"]/section[has-class("content-desc")]/p)[1]/span/@title', default=NotLoaded)
class item(ItemElement): klass = Account def obj_id(self): _id = CleanText('./td[1]')(self) _id = ''.join(i for i in _id if i.isdigit()) return _id def obj_label(self): label = Format('%s', CleanText('./td[2]'))(self) label = label.replace(" o ", " ") return label obj__login = CleanDecimal('./td[1]') obj_currency = Currency('./td[6]') obj__company = CleanText('./td[3]') obj_type = Account.TYPE_PERP def obj_balance(self): # The page can be randomly in french or english and # the valuations can be "€12,345.67" or "12 345,67 €" try: return CleanDecimal.French('./td[6]')(self) except NumberFormatError: return CleanDecimal.US('./td[6]')(self)
def obj_original_currency(self): currency_text = Dict( 'holdingDetailInformation/0/holdingDetailMultipleCurrencyInformation/1/currencyProductHoldingBookValueAmountCode' )(self) if currency_text: return Currency().filter(currency_text) else: return NotAvailable
class get_account(ItemElement): klass = Account obj_type = Account.TYPE_CHECKING obj__site = 'other' obj_balance = 0 obj_number = obj_id = CleanText('//tr[td[text()="Mon numéro de compte"]]/td[@class="droite"]', replace=[(' ', '')]) obj_coming = CleanDecimal('//div[@id="mod-paiementcomptant"]//tr[td[contains(text(),"débité le")]]/td[@class="droite"]', sign=lambda _: -1, default=0) obj_currency = Currency('//div[@id="mod-paiementcomptant"]//tr[td[starts-with(normalize-space(text()),"Montant disponible")]]/td[@class="droite"]')
class item(ItemElement): klass = Housing def condition(self): return Dict('cardType')(self) not in [ 'advertising', 'localExpert' ] and Dict('id', default=False)(self) obj_id = Dict('id') def obj_type(self): idType = int(Env('query_type')(self)) type = next(k for k, v in TYPES.items() if v == idType) if type == POSTS_TYPES.FURNISHED_RENT: # SeLoger does not let us discriminate between furnished and not furnished. return POSTS_TYPES.RENT return type def obj_title(self): return "{} - {} - {}".format( Dict('estateType')(self), " / ".join(Dict('tags')(self)), Field('location')(self)) def obj_advert_type(self): is_agency = Dict('contact/agencyId', default=False)(self) if is_agency: return ADVERT_TYPES.PROFESSIONAL else: return ADVERT_TYPES.PERSONAL obj_utilities = UTILITIES.EXCLUDED def obj_photos(self): photos = [] for photo in Dict('photos')(self): photos.append(HousingPhoto(photo)) return photos def obj_location(self): quartier = Dict('districtLabel')(self) quartier = quartier if quartier else '' ville = Dict('cityLabel')(self) ville = ville if ville else '' cp = Dict('zipCode')(self) cp = cp if cp else '' return u'%s %s (%s)' % (quartier, ville, cp) obj_url = Dict('classifiedURL') obj_text = Dict('description') obj_cost = CleanDecimal(Dict('pricing/price', default=NotLoaded), default=NotLoaded) obj_currency = Currency(Dict('pricing/price', default=NotLoaded), default=NotLoaded) obj_price_per_meter = CleanDecimal( Dict('pricing/squareMeterPrice'), default=PricePerMeterFilter)
class item(ItemElement): klass = Account class Type(Filter): def filter(self, label): for pattern, actype in AccountsPage.TYPES.iteritems(): if label.startswith(pattern): return actype return Account.TYPE_UNKNOWN obj__history_url = Link('./td[1]/a') obj_label = CleanText('./td[1]') obj_currency = Currency('//span[contains(text(), "Solde")]') obj_balance = CleanDecimal('./td[2]', replace_dots=True) obj_type = Type(Field('label')) # Last numbers replaced with XX... or we have to send sms to get RIB. obj_iban = NotAvailable # some accounts may appear on multiple areas, but the area where they come from is indicated obj__owner = CleanText( '(./preceding-sibling::tr[@class="LnMnTiers"])[last()]') def obj_id(self): history_url = Field('_history_url')(self) if history_url.startswith('javascript:'): # Market account page = self.page.browser.investment.go() area_id = Regexp( CleanText('//span[@class="CelMnTiersT1"]'), r'\((\d+)\)', default='')(page.doc) for tr in page.doc.xpath( './/table/tr[not(has-class("LnTit")) and not(has-class("LnTot"))]' ): # Try to match account with id and balance. if CleanText('./td[2]//a')(tr) == Field('label')(self) \ and CleanDecimal('./td[3]//a')(tr) == Field('balance')(self): acc_id = CleanText('./td[1]', replace=[(' ', '')])(tr) if area_id: # because the acc_id can be the same between multiple areas return '%s.%s' % (area_id, acc_id) return acc_id else: page = self.page.browser.open(history_url).page return Regexp(CleanText('//span[has-class("Rappel")]'), '(\d{18}) | (\d{3}\w\d{15})')(page.doc) def validate(self, obj): if obj.id is None: obj.id = obj.label.replace(' ', '') return True
class item(ItemElement): klass = Bill obj_id = Format('%s_%s', Env('subid'), CleanText(TableCell('id'))) obj_url = '/Account/CommandListingPage.aspx' obj_format = 'pdf' obj_price = CleanDecimal(TableCell('price'), replace_dots=True) obj_currency = Currency(TableCell('price')) def obj_date(self): return parse_french_date(CleanText(TableCell('date'))(self)).date()
class item(ItemElement): klass = Bill _num = Dict('document/id') obj_id = Format('%s_%s', Env('subid'), _num) obj_date = Eval(datetime.fromtimestamp, Dict('created_at')) obj_label = Format('Facture %s', Field('id')) obj_url = Dict('document/href') obj_price = CleanDecimal(Dict('amount/amount')) obj_currency = Currency(Dict('amount/currency')) obj_format = 'pdf'
class item(ItemElement): klass = Account obj_id = CleanText(TableCell('id'), replace=[(' ', '')]) obj_label = CleanText(TableCell('label')) obj_balance = CleanDecimal.French(TableCell('balance')) obj_currency = Currency(TableCell('balance')) obj_type = Account.TYPE_LIFE_INSURANCE def obj_url(self): return AbsoluteLink(TableCell('id')(self)[0].xpath('.//a'), default=NotAvailable)(self)
class get_account(ItemElement): klass = Account obj_type = Account.TYPE_CARD obj__site = 'other' def obj_label(self): return self.page.browser.card_name obj_id = CleanText('//tr[td[text()="Mon numéro de compte"]]/td[@class="droite"]', replace=[(' ', '')]) obj_balance = CleanDecimal('''//div[@id="mod-paiementcomptant"]//tr[td[starts-with(normalize-space(text()),"Disponible jusqu'au")]]/td[@class="droite"]''') obj_coming = CleanDecimal('''//div[@id="mod-paiementcomptant"]//tr[td[span[contains(text(),"prélevé le")]]]/td[@class="droite"]''', sign=lambda _: -1, default=0) obj_currency = Currency('''//div[@id="mod-paiementcomptant"]//tr[td[starts-with(normalize-space(text()),"Disponible jusqu'au")]]/td[@class="droite"]''')
class item(ItemElement): klass = Account # If user has professional accounts, owner_type must be defined OWNER_TYPE = { 'Mes avoirs professionnels': AccountOwnerType.ORGANIZATION, 'Mes avoirs personnels': AccountOwnerType.PRIVATE, 'Mes crédits personnels': AccountOwnerType.PRIVATE, } # MapIn because, in case of private account, we actually catch "Mes avoirs personnels Mes crédits personnels" with CleanText which both can be use to recognize the owner_type as PRIVATE obj_owner_type = MapIn(CleanText('.//form[@id]/ancestor::div/h2'), OWNER_TYPE, NotAvailable) obj_label = Label( CleanText( './/form[@id]/preceding-sibling::p/span[@class="hsbc-pib-text hsbc-pib-bloc-account-name" or @class="hsbc-pib-text--small"]' )) obj_type = AccountsType(Field('label')) obj_url = CleanText('.//form/@action') obj_currency = Currency('.//form[@id]/following-sibling::*[1]') obj__is_form = bool(CleanText('.//form/@id')) obj__amount = CleanDecimal.French( './/form[@id]/following-sibling::*[1]') def obj_balance(self): if Field('type')(self) == Account.TYPE_CARD: return Decimal(0) elif 'Mes crédits' in CleanText( './/ancestor::div[1]/preceding-sibling::*')(self): return -abs(Field('_amount')(self)) return Field('_amount')(self) def obj_coming(self): if Field('type')(self) == Account.TYPE_CARD: return Field('_amount')(self) return NotAvailable def obj_id(self): # Investment accounts and main account can have the same id _id = CleanText('.//form[@id]/preceding-sibling::*[1]/span[2]', replace=[('.', ''), (' ', '')])(self) if "Scpi" in Field('label')(self): return _id + ".SCPI" # Same problem with scpi accounts. if Field('type')(self) == Account.TYPE_MARKET: return _id + ".INVEST" # Cards are displayed like '4561 00XX XXXX 5813 - Carte à débit différé' if 'Carte' in _id: _id = Regexp(pattern=r'(.*)-Carte').filter(_id) return _id
class SeLogerItem(ItemElement): klass = Housing obj_id = CleanText('idAnnonce') def obj_type(self): idType = int(CleanText('idTypeTransaction')(self)) type = next(k for k, v in TYPES.items() if v == idType) if type == POSTS_TYPES.FURNISHED_RENT: # SeLoger does not let us discriminate between furnished and not # furnished. return POSTS_TYPES.RENT return type def obj_house_type(self): idType = CleanText('idTypeBien')(self) try: return next(k for k, v in RET.items() if v == idType) except StopIteration: return NotAvailable obj_title = Format( "%s %s%s - %s", CleanText('titre'), CleanText('surface'), CleanText('surfaceUnite'), CleanText('ville'), ) obj_date = DateTime(CleanText('dtFraicheur')) obj_cost = CleanDecimal('prix') obj_currency = Currency('prixUnite') obj_area = CleanDecimal('surface', default=NotAvailable) obj_price_per_meter = PricePerMeterFilter() obj_text = CleanText('descriptif') obj_rooms = CleanDecimal('nbPiece|nbPieces', default=NotAvailable) obj_bedrooms = CleanDecimal('nbChambre|nbChambres', default=NotAvailable) def obj_location(self): location = CleanText('adresse', default="")(self) quartier = CleanText('quartier', default=None)(self) if not location and quartier is not None: location = quartier ville = CleanText('ville')(self) cp = CleanText('cp')(self) return u'%s %s (%s)' % (location, ville, cp) obj_station = CleanText('proximite', default=NotAvailable) obj_url = CleanText('permaLien')
class item(ItemElement): klass = Account obj_id = CleanText('.//div[@class="infos-contrat"]//strong') obj_label = CleanText('.//div[@class="type-contrat"]//h2') obj_type = Account.TYPE_LIFE_INSURANCE obj_balance = CleanDecimal(CleanText('.//div[@class="col-right"]', children=False), replace_dots=True, default=NotAvailable) obj_currency = Currency( CleanText(u'.//div[@class="col-right"]', children=False, replace=[("Au", "")]))