Skip to content

Commit

Permalink
profile with latest control flow updated
Browse files Browse the repository at this point in the history
  • Loading branch information
msmannan00 committed Oct 31, 2024
1 parent 7c761c0 commit fb189a4
Show file tree
Hide file tree
Showing 31 changed files with 936 additions and 145 deletions.
4 changes: 2 additions & 2 deletions backend/globaleaks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
__author__ = 'GlobaLeaks'
__email__ = '[email protected]'
__copyright__ = '2011-2024 - GlobaLeaks'
__version__ = '5.0.20'
__version__ = '5.0.19'
__license__ = 'AGPL-3.0'

DATABASE_VERSION = 68
DATABASE_VERSION = 69
FIRST_DATABASE_VERSION_SUPPORTED = 52

# Add new languages as they are supported here! To do this retrieve the name of
Expand Down
63 changes: 53 additions & 10 deletions backend/globaleaks/db/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
import sys
import traceback
import warnings
from collections import defaultdict
from operator import or_

from sqlalchemy.exc import SAWarning
from globaleaks.rest.cache import Cache

from globaleaks import models, DATABASE_VERSION
from globaleaks.handlers.admin.https import db_load_tls_configs
Expand Down Expand Up @@ -68,7 +71,8 @@ def initialize_db(session):
:param session: An ORM session
"""
from globaleaks.handlers.admin import tenant
tenant.db_create(session, {'active': True, 'mode': 'default', 'name': 'GLOBALEAKS', 'subdomain': ''})
tenant.db_create(session, {'active': False, 'mode': 'default', 'profile_counter': 1000000, 'name': 'GLOBALEAKS', 'subdomain': ''},False)
tenant.db_create(session, {'active': True, 'mode': 'default', 'profile_counter': 1000000, 'name': 'GLOBALEAKS', 'subdomain': ''})


def update_db():
Expand Down Expand Up @@ -156,8 +160,17 @@ def sync_initialize_snimap(session):
for cfg in db_load_tls_configs(session):
State.snimap.load(cfg['tid'], cfg)

def update_cache(cfg, tid):
tenant_cache = State.tenants[tid].cache
if cfg.var_name in ['https_cert', 'tor_onion_key'] or cfg.var_name in ConfigFilters['node']:
tenant_cache[cfg.var_name] = cfg.value
elif cfg.var_name in ConfigFilters['notification']:
tenant_cache.setdefault('notification', {})[cfg.var_name] = cfg.value
elif cfg.var_name in ConfigFilters['node']:
tenant_cache[cfg.var_name] = cfg.value

def db_refresh_tenant_cache(session, to_refresh=None):

active_tids = set([tid[0] for tid in session.query(models.Tenant.id).filter(models.Tenant.active.is_(True))])

cached_tids = set(State.tenants.keys())
Expand Down Expand Up @@ -187,7 +200,21 @@ def db_refresh_tenant_cache(session, to_refresh=None):
if to_refresh is None or to_refresh == 1:
tids = active_tids
else:
tids = [to_refresh] if to_refresh in active_tids else []
if to_refresh in active_tids:
tids = [to_refresh]
if to_refresh < 1000001:
default_profile_exists = session.query(Config).filter_by(tid=to_refresh, var_name='default_profile').first()
if default_profile_exists:
tids.append(default_profile_exists.tid)

elif to_refresh > 1000001:
matching_tids = [tid[0] for tid in session.query(Config.tid).filter_by(var_name='default_profile', value=str(to_refresh)).all()]
tids.extend(matching_tids)

for tid in matching_tids:
Cache.invalidate(tid)
else:
tids = []

if not tids:
return
Expand All @@ -214,15 +241,31 @@ def db_refresh_tenant_cache(session, to_refresh=None):
.filter(models.EnabledLanguage.tid.in_(tids)):
State.tenants[tid].cache['languages_enabled'].append(lang)

for cfg in session.query(Config).filter(Config.tid.in_(tids)):
tenant_cache = State.tenants[cfg.tid].cache
configs = defaultdict(dict)
default_configs = {}

for cfg in session.query(Config).filter(or_(Config.tid.in_(tids), Config.tid == 1000001)):
if cfg.tid == 1000001:
default_configs[cfg.var_name] = cfg
else:
configs[cfg.tid][cfg.var_name] = cfg

if cfg.var_name in ['https_cert', 'tor_onion_key']:
tenant_cache[cfg.var_name] = cfg.value
elif cfg.var_name in ConfigFilters['node']:
tenant_cache[cfg.var_name] = cfg.value
elif cfg.var_name in ConfigFilters['notification']:
tenant_cache['notification'][cfg.var_name] = cfg.value
for var_name, default_cfg in default_configs.items():
for tid, tenant in list(configs.items()):
if "default_profile" in tenant:
profile_id = int(tenant["default_profile"].value)
profile = configs[profile_id]
else:
profile_id = None
profile = None

if to_refresh == 1 or to_refresh is None or (to_refresh < 1000001 and to_refresh == tid) or (1000001 < to_refresh == profile_id) or (1000001 < to_refresh == tid):
if var_name in tenant:
update_cache(tenant[var_name], tid)
elif profile and var_name in profile:
update_cache(profile[var_name], tid)
elif tid:
update_cache(default_cfg, tid)

for tid, mail, pub_key in session.query(models.User.tid, models.User.mail_address, models.User.pgp_key_public) \
.filter(models.User.role == 'admin',
Expand Down
77 changes: 39 additions & 38 deletions backend/globaleaks/db/migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from globaleaks.db.migrations.update_67 import \
InternalTip_v_66, ReceiverFile_v_66, Redaction_v_66, User_v_66, WhistleblowerFile_v_66
from globaleaks.db.migrations.update_68 import Subscriber_v_67
from globaleaks.db.migrations.update_69 import Tenant_v_68


from globaleaks.orm import get_engine, get_session, make_db_uri
Expand All @@ -47,44 +48,44 @@


migration_mapping = OrderedDict([
('ArchivedSchema', [models._ArchivedSchema, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('AuditLog', [-1, -1, AuditLog_v_61, 0, 0, 0, 0, 0, 0, 0, models._AuditLog, 0, 0, 0, 0, 0, 0]),
('Comment', [Comment_v_64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, models._Comment, 0, 0, 0]),
('Config', [models._Config, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('ConfigL10N', [models._ConfigL10N, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('Context', [Context_v_61, 0, 0, 0, 0, 0, 0, 0, 0, 0, Context_v_63, 0, models._Context, 0, 0, 0, 0]),
('CustomTexts', [models._CustomTexts, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('EnabledLanguage', [models._EnabledLanguage, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('Field', [models._Field, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('FieldAttr', [FieldAttr_v_52, models._FieldAttr, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('FieldOption', [models._FieldOption, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('FieldOptionTriggerField', [models._FieldOptionTriggerField, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('FieldOptionTriggerStep', [models._FieldOptionTriggerStep, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('File', [File_v_53, 0, models._File, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('IdentityAccessRequest', [IdentityAccessRequest_v_64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, models._IdentityAccessRequest, 0, 0, 0]),
('IdentityAccessRequestCustodian', [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, models._IdentityAccessRequestCustodian, 0, 0, 0]),
('InternalFile', [InternalFile_v_64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, models._InternalFile, 0, 0, 0]),
('InternalTip', [InternalTip_v_52, InternalTip_v_57, 0, 0, 0, 0, InternalTip_v_59, 0, InternalTip_v_63, 0, 0, 0, InternalTip_v_64, InternalTip_v_66, 0, models._InternalTip, 0]),
('InternalTipAnswers', [models._InternalTipAnswers, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('InternalTipData', [models._InternalTipData, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('Mail', [models._Mail, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('Message', [Message_v_64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1]),
('Questionnaire', [models._Questionnaire, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('ReceiverContext', [models._ReceiverContext, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('ReceiverFile', [ReceiverFile_v_57, 0, 0, 0, 0, 0, ReceiverFile_v_64, 0, 0, 0, 0, 0, 0, ReceiverFile_v_66, 0, models._ReceiverFile, 0]),
('ReceiverTip', [ReceiverTip_v_52, ReceiverTip_v_57, 0, 0, 0, 0, ReceiverTip_v_58, ReceiverTip_v_59, ReceiverTip_v_61, 0, ReceiverTip_v_64, 0, 0, models._ReceiverTip, 0, 0, 0]),
('Redaction', [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, Redaction_v_66, 0, models._Redaction, 0]),
('Redirect', [models._Redirect, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('SubmissionStatus', [SubmissionStatus_v_64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, models._SubmissionStatus, 0, 0]),
('SubmissionSubStatus', [SubmissionSubStatus_v_64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, SubmissionSubStatus_v_65, 0, models._SubmissionSubStatus, 0]),
('SubmissionStatusChange', [SubmissionStatusChange_v_54, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]),
('Step', [models._Step, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('Subscriber', [Subscriber_v_52, Subscriber_v_62, 0, 0, 0, 0, 0, 0, 0, 0, 0, Subscriber_v_67, 0, 0, 0, 0, models._Subscriber]),
('Tenant', [Tenant_v_52, models._Tenant, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('User', [User_v_52, User_v_54, 0, User_v_56, 0, User_v_61, 0, 0, 0, 0, User_v_64, 0, 0, User_v_66, 0, models._User, 0]),
('WhistleblowerFile', [WhistleblowerFile_v_57, 0, 0, 0, 0, 0, WhistleblowerFile_v_64, 0, 0, 0, 0, 0, 0, WhistleblowerFile_v_66, 0, models._WhistleblowerFile, 0]),

('WhistleblowerTip', [WhistleblowerTip_v_59, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1])
('ArchivedSchema', [models._ArchivedSchema, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('AuditLog', [-1, -1, AuditLog_v_61, 0, 0, 0, 0, 0, 0, 0, models._AuditLog, 0, 0, 0, 0, 0, 0, 0]),
('Comment', [Comment_v_64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, models._Comment, 0, 0, 0, 0]),
('Config', [models._Config, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('ConfigL10N', [models._ConfigL10N, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('Context', [Context_v_61, 0, 0, 0, 0, 0, 0, 0, 0, 0, Context_v_63, 0, models._Context, 0, 0, 0, 0, 0]),
('CustomTexts', [models._CustomTexts, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('EnabledLanguage', [models._EnabledLanguage, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('Field', [models._Field, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('FieldAttr', [FieldAttr_v_52, models._FieldAttr, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('FieldOption', [models._FieldOption, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('FieldOptionTriggerField', [models._FieldOptionTriggerField, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('FieldOptionTriggerStep', [models._FieldOptionTriggerStep, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('File', [File_v_53, 0, models._File, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('IdentityAccessRequest', [IdentityAccessRequest_v_64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, models._IdentityAccessRequest, 0, 0, 0, 0]),
('IdentityAccessRequestCustodian', [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, models._IdentityAccessRequestCustodian, 0, 0, 0, 0]),
('InternalFile', [InternalFile_v_64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, models._InternalFile, 0, 0, 0, 0]),
('InternalTip', [InternalTip_v_52, InternalTip_v_57, 0, 0, 0, 0, InternalTip_v_59, 0, InternalTip_v_63, 0, 0, 0, InternalTip_v_64, InternalTip_v_66, 0, models._InternalTip, 0, 0]),
('InternalTipAnswers', [models._InternalTipAnswers, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('InternalTipData', [models._InternalTipData, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('Mail', [models._Mail, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('Message', [Message_v_64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1]),
('Questionnaire', [models._Questionnaire, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('ReceiverContext', [models._ReceiverContext, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('ReceiverFile', [ReceiverFile_v_57, 0, 0, 0, 0, 0, ReceiverFile_v_64, 0, 0, 0, 0, 0, 0, ReceiverFile_v_66, 0, models._ReceiverFile, 0, 0]),
('ReceiverTip', [ReceiverTip_v_52, ReceiverTip_v_57, 0, 0, 0, 0, ReceiverTip_v_58, ReceiverTip_v_59, ReceiverTip_v_61, 0, ReceiverTip_v_64, 0, 0, models._ReceiverTip, 0, 0, 0, 0]),
('Redaction', [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, Redaction_v_66, 0, models._Redaction, 0, 0]),
('Redirect', [models._Redirect, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('SubmissionStatus', [SubmissionStatus_v_64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, models._SubmissionStatus, 0, 0, 0]),
('SubmissionSubStatus', [SubmissionSubStatus_v_64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, SubmissionSubStatus_v_65, 0, models._SubmissionSubStatus, 0, 0]),
('SubmissionStatusChange', [SubmissionStatusChange_v_54, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]),
('Step', [models._Step, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('Subscriber', [Subscriber_v_52, Subscriber_v_62, 0, 0, 0, 0, 0, 0, 0, 0, 0, Subscriber_v_67, 0, 0, 0, 0, models._Subscriber, 0]),
('Tenant', [Tenant_v_52, models._Tenant, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Tenant_v_68]),
('User', [User_v_52, User_v_54, 0, User_v_56, 0, User_v_61, 0, 0, 0, 0, User_v_64, 0, 0, User_v_66, 0, models._User, 0, 0]),
('WhistleblowerFile', [WhistleblowerFile_v_57, 0, 0, 0, 0, 0, WhistleblowerFile_v_64, 0, 0, 0, 0, 0, 0, WhistleblowerFile_v_66, 0, models._WhistleblowerFile, 0, 0]),

('WhistleblowerTip', [WhistleblowerTip_v_59, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1])
])


Expand Down
107 changes: 107 additions & 0 deletions backend/globaleaks/db/migrations/update_69/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# -*- coding: UTF-8 -*-
from globaleaks import models
from globaleaks.db.migrations.update import MigrationBase
from globaleaks.models import Model
from globaleaks.models.properties import *
from globaleaks.utils.utility import datetime_now
from globaleaks.models.config_desc import ConfigDescriptor
from globaleaks.models.config import get_default
from globaleaks.db.appdata import load_appdata
from sqlalchemy import tuple_

class Tenant_v_68(Model):
__tablename__ = 'tenant'
__table_args__ = {'sqlite_autoincrement': False}

id = Column(Integer, primary_key=True, autoincrement=False)
creation_date = Column(DateTime, default=datetime_now, nullable=False)
active = Column(Boolean, default=False, nullable=False)

class MigrationScript(MigrationBase):
default_tenant_keys = ["subdomain", "onionservice", "https_admin", "https_analyst", "https_cert" ,"wizard_done", "uuid", "mode", "default_language", "name"]

def migrate_Tenant(self):

old_tenants = self.session_old.query(self.model_from['Tenant']).all()
new_tenants = []
for old_obj in old_tenants:
new_tenant = self.model_to['Tenant']()
for key in new_tenant.__mapper__.column_attrs.keys():
setattr(new_tenant, key, getattr(old_obj, key, None))
new_tenants.append(new_tenant)

defualt_tenant = self.model_to['Tenant']()
defualt_tenant.id = 1000001
defualt_tenant.active = False
new_tenants.append(defualt_tenant)

self.session_new.add_all(new_tenants)
self.entries_count['Tenant'] = len(new_tenants)

def migrate_Config(self):

old_configs = self.session_old.query(self.model_from['Config']).all()
new_configs = []
for old_obj in old_configs:
new_obj = self.model_to['Config']()
for key in new_obj.__mapper__.column_attrs.keys():
setattr(new_obj, key, getattr(old_obj, key))
new_configs.append(new_obj)
self.session_new.bulk_save_objects(new_configs)

variables = {name: get_default(desc.default) for name, desc in ConfigDescriptor.items()}

variables.update({
'tenant_counter': self.session_old.query(self.model_from['Tenant']).count(),
'profile_counter': 1000000
})

merged_configs = []
for var_name, value in variables.items():
new_config = self.model_to['Config']()
new_config.tid = 1 if var_name in ['tenant_counter', 'profile_counter'] else 1000001
new_config.var_name = var_name
new_config.value = value
merged_configs.append(new_config)

self.session_new.bulk_save_objects(merged_configs)
self.entries_count['Config'] += len(merged_configs)

default_config = {entry.var_name: entry.value for entry in self.session_new.query(self.model_to['Config']).filter_by(tid=1000001).all()}
tenant_configs = self.session_new.query(self.model_to['Config'].tid,self.model_to['Config'].var_name,self.model_to['Config'].value).filter(self.model_to['Config'].tid.notin_([1000001, 1])).all()

to_delete = []
for tid, var_name, value in tenant_configs:
if var_name in default_config and value == default_config[var_name] and var_name not in self.default_tenant_keys:
to_delete.append((tid, var_name))

if to_delete:
self.session_new.query(self.model_to['Config']).filter(tuple_(self.model_to['Config'].tid, self.model_to['Config'].var_name).in_(to_delete)).delete(synchronize_session=False)
self.entries_count['Config'] -= len(to_delete)

def migrate_ConfigL10N(self):

old_configs = self.session_old.query(self.model_from['ConfigL10N']).all()
new_configs = []
for old_obj in old_configs:
new_obj = self.model_to['ConfigL10N']()
for key in new_obj.__mapper__.column_attrs.keys():
setattr(new_obj, key, getattr(old_obj, key))
new_configs.append(new_obj)
self.session_new.bulk_save_objects(new_configs)

models.config.add_new_lang(self.session_new, 1000001, 'en', load_appdata())
self.entries_count['ConfigL10N'] += 72
self.entries_count['EnabledLanguage'] += 1

default_config = {(entry.var_name, entry.lang): entry.value for entry in self.session_new.query(self.model_to['ConfigL10N']).filter_by(tid=1000001).all()}
tenant_configs = self.session_new.query(self.model_to['ConfigL10N'].tid,self.model_to['ConfigL10N'].var_name,self.model_to['ConfigL10N'].lang,self.model_to['ConfigL10N'].value).filter(self.model_to['ConfigL10N'].tid.notin_([1000001, 1])).all()

to_delete = []
for tid, var_name, lang, value in tenant_configs:
if (var_name, lang) in default_config and value == default_config[(var_name, lang)] and var_name not in self.default_tenant_keys:
to_delete.append((tid, var_name, lang))

if to_delete:
self.session_new.query(self.model_to['ConfigL10N']).filter(tuple_(self.model_to['ConfigL10N'].tid, self.model_to['ConfigL10N'].var_name, self.model_to['ConfigL10N'].lang).in_(to_delete)).delete(synchronize_session=False)
self.entries_count['ConfigL10N'] -= len(to_delete)
Loading

0 comments on commit fb189a4

Please sign in to comment.