Use namedtuples for results from the DB
This makes the code much easier to read, and much less error prone.
This commit is contained in:
parent
84f48a400c
commit
ef38afcf90
|
@ -1424,24 +1424,24 @@ class ChatControl(ChatControlBase):
|
||||||
|
|
||||||
local_old_kind = None
|
local_old_kind = None
|
||||||
self.conv_textview.just_cleared = True
|
self.conv_textview.just_cleared = True
|
||||||
for row in rows: # row[0] time, row[1] has kind, row[2] the message, row[3] subject, row[4] additional_data
|
for row in rows: # time, kind, message, subject, additional_data
|
||||||
msg = row[2]
|
msg = row.message
|
||||||
additional_data = row[4]
|
additional_data = row.additional_data
|
||||||
if not msg: # message is empty, we don't print it
|
if not msg: # message is empty, we don't print it
|
||||||
continue
|
continue
|
||||||
if row[1] in (KindConstant.CHAT_MSG_SENT,
|
if row.kind in (KindConstant.CHAT_MSG_SENT,
|
||||||
KindConstant.SINGLE_MSG_SENT):
|
KindConstant.SINGLE_MSG_SENT):
|
||||||
kind = 'outgoing'
|
kind = 'outgoing'
|
||||||
name = self.get_our_nick()
|
name = self.get_our_nick()
|
||||||
elif row[1] in (KindConstant.SINGLE_MSG_RECV,
|
elif row.kind in (KindConstant.SINGLE_MSG_RECV,
|
||||||
KindConstant.CHAT_MSG_RECV):
|
KindConstant.CHAT_MSG_RECV):
|
||||||
kind = 'incoming'
|
kind = 'incoming'
|
||||||
name = self.contact.get_shown_name()
|
name = self.contact.get_shown_name()
|
||||||
elif row[1] == KindConstant.ERROR:
|
elif row.kind == KindConstant.ERROR:
|
||||||
kind = 'status'
|
kind = 'status'
|
||||||
name = self.contact.get_shown_name()
|
name = self.contact.get_shown_name()
|
||||||
|
|
||||||
tim = float(row[0])
|
tim = float(row.time)
|
||||||
|
|
||||||
if gajim.config.get('restored_messages_small'):
|
if gajim.config.get('restored_messages_small'):
|
||||||
small_attr = ['small']
|
small_attr = ['small']
|
||||||
|
@ -1450,14 +1450,14 @@ class ChatControl(ChatControlBase):
|
||||||
xhtml = None
|
xhtml = None
|
||||||
if msg.startswith('<body '):
|
if msg.startswith('<body '):
|
||||||
xhtml = msg
|
xhtml = msg
|
||||||
if row[3]:
|
if row.subject:
|
||||||
msg = _('Subject: %(subject)s\n%(message)s') % \
|
msg = _('Subject: %(subject)s\n%(message)s') % \
|
||||||
{'subject': row[3], 'message': msg}
|
{'subject': row.subject, 'message': msg}
|
||||||
ChatControlBase.print_conversation_line(self, msg, kind, name,
|
ChatControlBase.print_conversation_line(self, msg, kind, name,
|
||||||
tim, small_attr, small_attr + ['restored_message'],
|
tim, small_attr, small_attr + ['restored_message'],
|
||||||
small_attr + ['restored_message'], False,
|
small_attr + ['restored_message'], False,
|
||||||
old_kind=local_old_kind, xhtml=xhtml, additional_data=additional_data)
|
old_kind=local_old_kind, xhtml=xhtml, additional_data=additional_data)
|
||||||
if row[2].startswith('/me ') or row[2].startswith('/me\n'):
|
if row.message.startswith('/me ') or row.message.startswith('/me\n'):
|
||||||
local_old_kind = None
|
local_old_kind = None
|
||||||
else:
|
else:
|
||||||
local_old_kind = kind
|
local_old_kind = kind
|
||||||
|
|
|
@ -121,6 +121,20 @@ class Logger:
|
||||||
gajim.ged.register_event_handler('gc-message-received',
|
gajim.ged.register_event_handler('gc-message-received',
|
||||||
ged.POSTCORE, self._nec_gc_message_received)
|
ged.POSTCORE, self._nec_gc_message_received)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def namedtuple_factory(cursor, row):
|
||||||
|
"""
|
||||||
|
Usage:
|
||||||
|
con.row_factory = namedtuple_factory
|
||||||
|
"""
|
||||||
|
fields = [col[0] for col in cursor.description]
|
||||||
|
Row = namedtuple("Row", fields)
|
||||||
|
named_row = Row(*row)
|
||||||
|
if 'additional_data' in fields:
|
||||||
|
named_row = named_row._replace(
|
||||||
|
additional_data=json.loads(named_row.additional_data))
|
||||||
|
return named_row
|
||||||
|
|
||||||
def dispatch(self, event, error):
|
def dispatch(self, event, error):
|
||||||
gajim.ged.raise_event(event, None, str(error))
|
gajim.ged.raise_event(event, None, str(error))
|
||||||
|
|
||||||
|
@ -145,6 +159,7 @@ class Logger:
|
||||||
self.con = sqlite.connect(LOG_DB_FILE, timeout=20.0,
|
self.con = sqlite.connect(LOG_DB_FILE, timeout=20.0,
|
||||||
isolation_level='IMMEDIATE')
|
isolation_level='IMMEDIATE')
|
||||||
os.chdir(back)
|
os.chdir(back)
|
||||||
|
self.con.row_factory = self.namedtuple_factory
|
||||||
self.cur = self.con.cursor()
|
self.cur = self.con.cursor()
|
||||||
self.set_synchronous(False)
|
self.set_synchronous(False)
|
||||||
|
|
||||||
|
@ -191,18 +206,16 @@ class Logger:
|
||||||
def get_jids_already_in_db(self):
|
def get_jids_already_in_db(self):
|
||||||
try:
|
try:
|
||||||
self.cur.execute('SELECT jid FROM jids')
|
self.cur.execute('SELECT jid FROM jids')
|
||||||
# list of tuples: [('aaa@bbb',), ('cc@dd',)]
|
|
||||||
rows = self.cur.fetchall()
|
rows = self.cur.fetchall()
|
||||||
except sqlite.DatabaseError:
|
except sqlite.DatabaseError:
|
||||||
raise exceptions.DatabaseMalformed(LOG_DB_PATH)
|
raise exceptions.DatabaseMalformed(LOG_DB_PATH)
|
||||||
self.jids_already_in = []
|
self.jids_already_in = []
|
||||||
for row in rows:
|
for row in rows:
|
||||||
# row[0] is first item of row (the only result here, the jid)
|
if not row.jid:
|
||||||
if row[0] == '':
|
|
||||||
# malformed jid, ignore line
|
# malformed jid, ignore line
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
self.jids_already_in.append(row[0])
|
self.jids_already_in.append(row.jid)
|
||||||
|
|
||||||
def get_jids_in_db(self):
|
def get_jids_in_db(self):
|
||||||
return self.jids_already_in
|
return self.jids_already_in
|
||||||
|
@ -232,7 +245,7 @@ class Logger:
|
||||||
if row is None:
|
if row is None:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
if row[0] == JIDConstant.ROOM_TYPE:
|
if row.type == JIDConstant.ROOM_TYPE:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -253,7 +266,7 @@ class Logger:
|
||||||
self.cur.execute('SELECT jid_id FROM jids WHERE jid=?', [jid])
|
self.cur.execute('SELECT jid_id FROM jids WHERE jid=?', [jid])
|
||||||
row = self.cur.fetchone()
|
row = self.cur.fetchone()
|
||||||
if row:
|
if row:
|
||||||
return row[0]
|
return row.jid_id
|
||||||
# oh! a new jid :), we add it now
|
# oh! a new jid :), we add it now
|
||||||
if typestr == 'ROOM':
|
if typestr == 'ROOM':
|
||||||
typ = JIDConstant.ROOM_TYPE
|
typ = JIDConstant.ROOM_TYPE
|
||||||
|
@ -476,8 +489,8 @@ class Logger:
|
||||||
except Exception:
|
except Exception:
|
||||||
unread_results = []
|
unread_results = []
|
||||||
for message in unread_results:
|
for message in unread_results:
|
||||||
msg_log_id = message[0]
|
msg_log_id = message.message_id
|
||||||
shown = message[1]
|
shown = message.shown
|
||||||
# here we get infos for that message, and related jid from jids table
|
# here we get infos for that message, and related jid from jids table
|
||||||
# do NOT change order of SELECTed things, unless you change function(s)
|
# do NOT change order of SELECTed things, unless you change function(s)
|
||||||
# that called this function
|
# that called this function
|
||||||
|
@ -488,15 +501,13 @@ class Logger:
|
||||||
WHERE logs.log_line_id = %d AND logs.jid_id = jids.jid_id
|
WHERE logs.log_line_id = %d AND logs.jid_id = jids.jid_id
|
||||||
''' % msg_log_id
|
''' % msg_log_id
|
||||||
)
|
)
|
||||||
results = self.cur.fetchall()
|
results = self.cur.fetchone()
|
||||||
if len(results) == 0:
|
if len(results) == 0:
|
||||||
# Log line is no more in logs table. remove it from unread_messages
|
# Log line is no more in logs table. remove it from unread_messages
|
||||||
self.set_read_messages([msg_log_id])
|
self.set_read_messages([msg_log_id])
|
||||||
continue
|
continue
|
||||||
results[0] = list(results[0])
|
|
||||||
results[0][5] = json.loads(results[0][5])
|
all_messages.append((results, shown))
|
||||||
results[0].append(shown)
|
|
||||||
all_messages.append(results[0])
|
|
||||||
return all_messages
|
return all_messages
|
||||||
|
|
||||||
def write(self, kind, jid, message=None, show=None, tim=None, subject=None,
|
def write(self, kind, jid, message=None, show=None, tim=None, subject=None,
|
||||||
|
@ -619,12 +630,7 @@ class Logger:
|
||||||
KindConstant.CHAT_MSG_SENT, KindConstant.ERROR, timeout,
|
KindConstant.CHAT_MSG_SENT, KindConstant.ERROR, timeout,
|
||||||
restore_how_many, pending_how_many), jid_tuple)
|
restore_how_many, pending_how_many), jid_tuple)
|
||||||
|
|
||||||
results = self.cur.fetchall()
|
messages = self.cur.fetchall()
|
||||||
messages = []
|
|
||||||
for entry in results:
|
|
||||||
additional_data = json.loads(entry[4])
|
|
||||||
parsed_entry = entry[:4] + (additional_data, ) + entry[5:]
|
|
||||||
messages.append(parsed_entry)
|
|
||||||
except sqlite.DatabaseError:
|
except sqlite.DatabaseError:
|
||||||
self.dispatch('DB_ERROR',
|
self.dispatch('DB_ERROR',
|
||||||
exceptions.DatabaseMalformed(LOG_DB_PATH))
|
exceptions.DatabaseMalformed(LOG_DB_PATH))
|
||||||
|
@ -643,10 +649,6 @@ class Logger:
|
||||||
start_of_day = int(time.mktime(local_time))
|
start_of_day = int(time.mktime(local_time))
|
||||||
return start_of_day
|
return start_of_day
|
||||||
|
|
||||||
Message = namedtuple('Message',
|
|
||||||
['contact_name', 'time', 'kind', 'show', 'message', 'subject',
|
|
||||||
'additional_data', 'log_line_id'])
|
|
||||||
|
|
||||||
def get_conversation_for_date(self, jid, year, month, day, account):
|
def get_conversation_for_date(self, jid, year, month, day, account):
|
||||||
"""
|
"""
|
||||||
Load the complete conversation with a given jid on a specific date
|
Load the complete conversation with a given jid on a specific date
|
||||||
|
@ -680,11 +682,7 @@ class Logger:
|
||||||
ORDER BY time
|
ORDER BY time
|
||||||
''' % (where_sql, start_of_day, last_second_of_day), jid_tuple)
|
''' % (where_sql, start_of_day, last_second_of_day), jid_tuple)
|
||||||
|
|
||||||
results = [self.Message(*row) for row in self.cur.fetchall()]
|
return self.cur.fetchall()
|
||||||
for message in results:
|
|
||||||
message._replace(additional_data=json.loads(message.additional_data))
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
def search_log(self, jid, query, account, year=None, month=None, day=None):
|
def search_log(self, jid, query, account, year=None, month=None, day=None):
|
||||||
"""
|
"""
|
||||||
|
@ -728,11 +726,7 @@ class Logger:
|
||||||
ORDER BY time
|
ORDER BY time
|
||||||
''' % (where_sql, like_sql), jid_tuple)
|
''' % (where_sql, like_sql), jid_tuple)
|
||||||
|
|
||||||
results = [self.Message(*row) for row in self.cur.fetchall()]
|
return self.cur.fetchall()
|
||||||
for message in results:
|
|
||||||
message._replace(additional_data=json.loads(message.additional_data))
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
def get_days_with_logs(self, jid, year, month, max_day, account):
|
def get_days_with_logs(self, jid, year, month, max_day, account):
|
||||||
"""
|
"""
|
||||||
|
@ -756,7 +750,7 @@ class Logger:
|
||||||
# and take only one of the same values (distinct)
|
# and take only one of the same values (distinct)
|
||||||
# Now we have timestamps of time 0:00 of every day with logs
|
# Now we have timestamps of time 0:00 of every day with logs
|
||||||
self.cur.execute('''
|
self.cur.execute('''
|
||||||
SELECT DISTINCT time/(86400)*86400 FROM logs
|
SELECT DISTINCT time/(86400)*86400 as time FROM logs
|
||||||
WHERE (%s)
|
WHERE (%s)
|
||||||
AND time BETWEEN %d AND %d
|
AND time BETWEEN %d AND %d
|
||||||
AND kind NOT IN (%d, %d)
|
AND kind NOT IN (%d, %d)
|
||||||
|
@ -767,7 +761,7 @@ class Logger:
|
||||||
|
|
||||||
# convert timestamps to day of month
|
# convert timestamps to day of month
|
||||||
for line in result:
|
for line in result:
|
||||||
days_with_logs[0:0]=[time.gmtime(line[0])[2]]
|
days_with_logs[0:0]=[time.gmtime(line.time)[2]]
|
||||||
|
|
||||||
return days_with_logs
|
return days_with_logs
|
||||||
|
|
||||||
|
@ -788,7 +782,7 @@ class Logger:
|
||||||
where_sql = 'jid_id = ?'
|
where_sql = 'jid_id = ?'
|
||||||
jid_tuple = (jid_id,)
|
jid_tuple = (jid_id,)
|
||||||
self.cur.execute('''
|
self.cur.execute('''
|
||||||
SELECT MAX(time) FROM logs
|
SELECT MAX(time) as time FROM logs
|
||||||
WHERE (%s)
|
WHERE (%s)
|
||||||
AND kind NOT IN (%d, %d)
|
AND kind NOT IN (%d, %d)
|
||||||
''' % (where_sql, KindConstant.STATUS, KindConstant.GCSTATUS),
|
''' % (where_sql, KindConstant.STATUS, KindConstant.GCSTATUS),
|
||||||
|
@ -796,7 +790,7 @@ class Logger:
|
||||||
|
|
||||||
results = self.cur.fetchone()
|
results = self.cur.fetchone()
|
||||||
if results is not None:
|
if results is not None:
|
||||||
result = results[0]
|
result = results.time
|
||||||
else:
|
else:
|
||||||
result = None
|
result = None
|
||||||
return result
|
return result
|
||||||
|
@ -819,7 +813,7 @@ class Logger:
|
||||||
|
|
||||||
results = self.cur.fetchone()
|
results = self.cur.fetchone()
|
||||||
if results is not None:
|
if results is not None:
|
||||||
result = results[0]
|
result = results.time
|
||||||
else:
|
else:
|
||||||
result = None
|
result = None
|
||||||
return result
|
return result
|
||||||
|
@ -870,10 +864,9 @@ class Logger:
|
||||||
return
|
return
|
||||||
self.cur.execute(
|
self.cur.execute(
|
||||||
'SELECT type from transports_cache WHERE transport = "%s"' % jid)
|
'SELECT type from transports_cache WHERE transport = "%s"' % jid)
|
||||||
results = self.cur.fetchall()
|
results = self.cur.fetchone()
|
||||||
if results:
|
if results:
|
||||||
result = results[0][0]
|
if results.type == type_id:
|
||||||
if result == type_id:
|
|
||||||
return
|
return
|
||||||
sql = 'UPDATE transports_cache SET type = %d WHERE transport = "%s"' %\
|
sql = 'UPDATE transports_cache SET type = %d WHERE transport = "%s"' %\
|
||||||
(type_id, jid)
|
(type_id, jid)
|
||||||
|
@ -893,8 +886,8 @@ class Logger:
|
||||||
return {}
|
return {}
|
||||||
answer = {}
|
answer = {}
|
||||||
for result in results:
|
for result in results:
|
||||||
answer[result[0]] = self.convert_api_values_to_human_transport_type(
|
answer[result.transport] = self.convert_api_values_to_human_transport_type(
|
||||||
result[1])
|
result.type)
|
||||||
return answer
|
return answer
|
||||||
|
|
||||||
# A longer note here:
|
# A longer note here:
|
||||||
|
@ -924,16 +917,16 @@ class Logger:
|
||||||
|
|
||||||
# list of corrupted entries that will be removed
|
# list of corrupted entries that will be removed
|
||||||
to_be_removed = []
|
to_be_removed = []
|
||||||
for hash_method, hash_, data in self.cur:
|
for row in self.cur:
|
||||||
# for each row: unpack the data field
|
# for each row: unpack the data field
|
||||||
# (format: (category, type, name, category, type, name, ...
|
# (format: (category, type, name, category, type, name, ...
|
||||||
# ..., 'FEAT', feature1, feature2, ...).join(' '))
|
# ..., 'FEAT', feature1, feature2, ...).join(' '))
|
||||||
# NOTE: if there's a need to do more gzip, put that to a function
|
# NOTE: if there's a need to do more gzip, put that to a function
|
||||||
try:
|
try:
|
||||||
data = GzipFile(fileobj=BytesIO(data)).read().decode('utf-8').split('\0')
|
data = GzipFile(fileobj=BytesIO(row.data)).read().decode('utf-8').split('\0')
|
||||||
except IOError:
|
except IOError:
|
||||||
# This data is corrupted. It probably contains non-ascii chars
|
# This data is corrupted. It probably contains non-ascii chars
|
||||||
to_be_removed.append((hash_method, hash_))
|
to_be_removed.append((row.hash_method, row.hash))
|
||||||
continue
|
continue
|
||||||
i = 0
|
i = 0
|
||||||
identities = list()
|
identities = list()
|
||||||
|
@ -952,7 +945,7 @@ class Logger:
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
# yield the row
|
# yield the row
|
||||||
yield hash_method, hash_, identities, features
|
yield row.hash_method, row.hash, identities, features
|
||||||
for hash_method, hash_ in to_be_removed:
|
for hash_method, hash_ in to_be_removed:
|
||||||
sql = '''DELETE FROM caps_cache WHERE hash_method = "%s" AND
|
sql = '''DELETE FROM caps_cache WHERE hash_method = "%s" AND
|
||||||
hash = "%s"''' % (hash_method, hash_)
|
hash = "%s"''' % (hash_method, hash_)
|
||||||
|
@ -1091,9 +1084,10 @@ class Logger:
|
||||||
SELECT j.jid, re.jid_id, re.name, re.subscription, re.ask
|
SELECT j.jid, re.jid_id, re.name, re.subscription, re.ask
|
||||||
FROM roster_entry re, jids j
|
FROM roster_entry re, jids j
|
||||||
WHERE re.account_jid_id=? AND j.jid_id=re.jid_id''', (account_jid_id,))
|
WHERE re.account_jid_id=? AND j.jid_id=re.jid_id''', (account_jid_id,))
|
||||||
for jid, jid_id, name, subscription, ask in self.cur:
|
for row in self.cur:
|
||||||
jid = jid
|
#jid, jid_id, name, subscription, ask
|
||||||
name = name
|
jid = row.jid
|
||||||
|
name = row.name
|
||||||
data[jid] = {}
|
data[jid] = {}
|
||||||
if name:
|
if name:
|
||||||
data[jid]['name'] = name
|
data[jid]['name'] = name
|
||||||
|
@ -1101,14 +1095,14 @@ class Logger:
|
||||||
data[jid]['name'] = None
|
data[jid]['name'] = None
|
||||||
data[jid]['subscription'] = \
|
data[jid]['subscription'] = \
|
||||||
self.convert_db_api_values_to_human_subscription_values(
|
self.convert_db_api_values_to_human_subscription_values(
|
||||||
subscription)
|
row.subscription)
|
||||||
data[jid]['groups'] = []
|
data[jid]['groups'] = []
|
||||||
data[jid]['resources'] = {}
|
data[jid]['resources'] = {}
|
||||||
if ask:
|
if row.ask:
|
||||||
data[jid]['ask'] = 'subscribe'
|
data[jid]['ask'] = 'subscribe'
|
||||||
else:
|
else:
|
||||||
data[jid]['ask'] = None
|
data[jid]['ask'] = None
|
||||||
data[jid]['id'] = jid_id
|
data[jid]['id'] = row.jid_id
|
||||||
|
|
||||||
# Then we add group for roster entries
|
# Then we add group for roster entries
|
||||||
for jid in data:
|
for jid in data:
|
||||||
|
@ -1116,8 +1110,8 @@ class Logger:
|
||||||
SELECT group_name FROM roster_group
|
SELECT group_name FROM roster_group
|
||||||
WHERE account_jid_id=? AND jid_id=?''',
|
WHERE account_jid_id=? AND jid_id=?''',
|
||||||
(account_jid_id, data[jid]['id']))
|
(account_jid_id, data[jid]['id']))
|
||||||
for (group_name,) in self.cur:
|
for row in self.cur:
|
||||||
group_name = group_name
|
group_name = row.group_name
|
||||||
data[jid]['groups'].append(group_name)
|
data[jid]['groups'].append(group_name)
|
||||||
del data[jid]['id']
|
del data[jid]['id']
|
||||||
|
|
||||||
|
|
|
@ -1842,10 +1842,9 @@ class RosterWindow:
|
||||||
old unread messages, delete them from unread table
|
old unread messages, delete them from unread table
|
||||||
"""
|
"""
|
||||||
results = gajim.logger.get_unread_msgs()
|
results = gajim.logger.get_unread_msgs()
|
||||||
for result in results:
|
for result, shown in results:
|
||||||
jid = result[4]
|
jid = result.jid
|
||||||
additional_data = result[5]
|
additional_data = result.additional_data
|
||||||
shown = result[6]
|
|
||||||
if gajim.contacts.get_first_contact_from_jid(account, jid) and not \
|
if gajim.contacts.get_first_contact_from_jid(account, jid) and not \
|
||||||
shown:
|
shown:
|
||||||
# We have this jid in our contacts list
|
# We have this jid in our contacts list
|
||||||
|
@ -1853,17 +1852,17 @@ class RosterWindow:
|
||||||
# with them
|
# with them
|
||||||
session = gajim.connections[account].make_new_session(jid)
|
session = gajim.connections[account].make_new_session(jid)
|
||||||
|
|
||||||
tim = float(result[2])
|
tim = float(result.time)
|
||||||
session.roster_message(jid, result[1], tim, msg_type='chat',
|
session.roster_message(jid, result.message, tim, msg_type='chat',
|
||||||
msg_log_id=result[0], additional_data=additional_data)
|
msg_log_id=result.log_line_id, additional_data=additional_data)
|
||||||
gajim.logger.set_shown_unread_msgs(result[0])
|
gajim.logger.set_shown_unread_msgs(result.log_line_id)
|
||||||
|
|
||||||
elif (time.time() - result[2]) > 2592000:
|
elif (time.time() - result.time) > 2592000:
|
||||||
# ok, here we see that we have a message in unread messages
|
# ok, here we see that we have a message in unread messages
|
||||||
# table that is older than a month. It is probably from someone
|
# table that is older than a month. It is probably from someone
|
||||||
# not in our roster for accounts we usually launch, so we will
|
# not in our roster for accounts we usually launch, so we will
|
||||||
# delete this id from unread message tables.
|
# delete this id from unread message tables.
|
||||||
gajim.logger.set_read_messages([result[0]])
|
gajim.logger.set_read_messages([result.log_line_id])
|
||||||
|
|
||||||
def fill_contacts_and_groups_dicts(self, array, account):
|
def fill_contacts_and_groups_dicts(self, array, account):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue