<commit_before>"""
Base settings to build other settings files upon.
"""
from django.utils.translation import ugettext_lazy as _

import environ

ROOT_DIR = environ.Path(__file__) - 3  # (pyfreebilling/config/settings/base.py - 3 = pyfreebilling/)
APPS_DIR = ROOT_DIR.path('pyfreebilling')

env = environ.Env()

READ_DOT_ENV_FILE = env.bool('DJANGO_READ_DOT_ENV_FILE', default=False)
if READ_DOT_ENV_FILE:
    # OS environment variables take precedence over variables from .env
    env.read_env(str(ROOT_DIR.path('.env')))

# GENERAL
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#debug
DEBUG = env.bool('DJANGO_DEBUG', False)
# Local time zone. Choices are
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# though not all of them may be available with every OS.
# In Windows, this must be set to your system time zone.
TIME_ZONE = 'UTC'
# https://docs.djangoproject.com/en/dev/ref/settings/#language-code
LANGUAGE_CODE = 'en-us'
# https://docs.djangoproject.com/en/dev/ref/settings/#site-id
SITE_ID = 1
# https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n
USE_I18N = True
# https://docs.djangoproject.com/en/dev/ref/settings/#use-l10n
USE_L10N = True
# https://docs.djangoproject.com/en/dev/ref/settings/#use-tz
USE_TZ = True

# DATABASES
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#databases
DATABASES = {
    'default': env.db('DATABASE_URL'),
}
DATABASES['default']['ATOMIC_REQUESTS'] = True

# URLS
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#root-urlconf
ROOT_URLCONF = 'config.urls'
# https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application
WSGI_APPLICATION = 'config.wsgi.application'

# APPS
# ------------------------------------------------------------------------------
DJANGO_APPS = [
    'fluent_dashboard',
    'admin_tools',     # for staticfiles in Django 1.3
    'admin_tools.theming',
    'admin_tools.menu',
    'admin_tools.dashboard',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 'django.contrib.humanize', # Handy template tags
    'django.contrib.admin',
]
THIRD_PARTY_APPS = [
    'django_extensions',
    'crispy_forms',
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    'rest_framework',
    'import_export',
    'django_countries',
    'constance',
    'migrate_sql',
]
PYFB_APPS = [
    'pyfb_kamailio',
    'pyfb_normalization',
    'pyfb_company',
    'pyfb_direction',
    'pyfb_endpoint',
    'pyfb_rating',
    'pyfb_routing',
    'pyfb_did',
    'pyfb_reporting',
]
LOCAL_APPS = [
    'pyfreebilling.users.apps.UsersAppConfig',
    # Your stuff: custom apps go here
]
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + PYFB_APPS + LOCAL_APPS

# MIGRATIONS
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#migration-modules
MIGRATION_MODULES = {
    'sites': 'pyfreebilling.contrib.sites.migrations'
}

# AUTHENTICATION
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#authentication-backends
AUTHENTICATION_BACKENDS = [
    'django.contrib.auth.backends.ModelBackend',
    'allauth.account.auth_backends.AuthenticationBackend',
]
# https://docs.djangoproject.com/en/dev/ref/settings/#auth-user-model
AUTH_USER_MODEL = 'users.User'
# https://docs.djangoproject.com/en/dev/ref/settings/#login-redirect-url
LOGIN_REDIRECT_URL = 'users:redirect'
# https://docs.djangoproject.com/en/dev/ref/settings/#login-url
LOGIN_URL = 'account_login'

# PASSWORDS
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers
PASSWORD_HASHERS = [
    # https://docs.djangoproject.com/en/dev/topics/auth/passwords/#using-argon2-with-django
    'django.contrib.auth.hashers.Argon2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
    'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
    'django.contrib.auth.hashers.BCryptPasswordHasher',
]
# https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# MIDDLEWARE
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#middleware
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# STATIC
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#static-root
STATIC_ROOT = str(ROOT_DIR('staticfiles'))
# https://docs.djangoproject.com/en/dev/ref/settings/#static-url
STATIC_URL = '/static/'
# https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS
STATICFILES_DIRS = [
    str(APPS_DIR.path('static')),
]
# https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders
STATICFILES_FINDERS = [
    'django.contrib.staticfiles.finders.FileSystemFinder',
    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
]

# MEDIA
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#media-root
MEDIA_ROOT = str(APPS_DIR('media'))
# https://docs.djangoproject.com/en/dev/ref/settings/#media-url
MEDIA_URL = '/media/'

# TEMPLATES
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#templates
TEMPLATES = [
    {
        # https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATES-BACKEND
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        # https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs
        'DIRS': [
            str(APPS_DIR.path('templates')),
        ],
        'OPTIONS': {
            # https://docs.djangoproject.com/en/dev/ref/settings/#template-debug
            'debug': DEBUG,
            # https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders
            # https://docs.djangoproject.com/en/dev/ref/templates/api/#loader-types
            'loaders': [
                'admin_tools.template_loaders.Loader',
                'django.template.loaders.filesystem.Loader',
                'django.template.loaders.app_directories.Loader',
            ],
            # https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.template.context_processors.i18n',
                'django.template.context_processors.media',
                'django.template.context_processors.static',
                'django.template.context_processors.tz',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]
# http://django-crispy-forms.readthedocs.io/en/latest/install.html#template-packs
CRISPY_TEMPLATE_PACK = 'bootstrap4'

# FIXTURES
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#fixture-dirs
FIXTURE_DIRS = (
    str(APPS_DIR.path('fixtures')),
)

# SECURITY
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-httponly
SESSION_COOKIE_HTTPONLY = True
# https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-httponly
CSRF_COOKIE_HTTPONLY = True
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-browser-xss-filter
SECURE_BROWSER_XSS_FILTER = True
# https://docs.djangoproject.com/en/dev/ref/settings/#x-frame-options
X_FRAME_OPTIONS = 'DENY'

# EMAIL
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
EMAIL_BACKEND = env('DJANGO_EMAIL_BACKEND', default='django.core.mail.backends.smtp.EmailBackend')

# ADMIN
# ------------------------------------------------------------------------------
# Django Admin URL.
ADMIN_URL = 'admin/'
# https://docs.djangoproject.com/en/dev/ref/settings/#admins
ADMINS = [
    ("""Mathias WOLFF""", 'mathias@celea.org'),
]
# https://docs.djangoproject.com/en/dev/ref/settings/#managers
MANAGERS = ADMINS

# django-allauth
# ------------------------------------------------------------------------------
ACCOUNT_ALLOW_REGISTRATION = env.bool('DJANGO_ACCOUNT_ALLOW_REGISTRATION', True)
# https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_AUTHENTICATION_METHOD = 'username'
# https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_EMAIL_REQUIRED = True
# https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_EMAIL_VERIFICATION = 'mandatory'
# https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_ADAPTER = 'pyfreebilling.users.adapters.AccountAdapter'
# https://django-allauth.readthedocs.io/en/latest/configuration.html
SOCIALACCOUNT_ADAPTER = 'pyfreebilling.users.adapters.SocialAccountAdapter'

# Your stuff...
# ------------------------------------------------------------------------------
# Location of root django.contrib.admin URL
HONEYPOT_URL = r'admin/'
ADMIN_URL = r'extranet/'

# Settings in constance
CONSTANCE_CONFIG = {
    'THE_ANSWER': (42, 'Answer to the Ultimate Question of Life, '
                       'The Universe, and Everything'),
}

# ADMIN SETTINGS
ADMIN_SITE_NAME = 'PyFreeBilling'
ADMIN_SITE_TITLE = 'PyFreeBilling administration'
ADMIN_SITE_DESCRIPTION = 'Softswitch and billing application'
ADMIN_TOOLS_INDEX_DASHBOARD = 'fluent_dashboard.dashboard.FluentIndexDashboard'
ADMIN_TOOLS_APP_INDEX_DASHBOARD = 'fluent_dashboard.dashboard.FluentAppIndexDashboard'
ADMIN_TOOLS_MENU = 'config.menu.CustomMenu'
ADMIN_TOOLS_THEMING_CSS = 'css/theming.css'

# dashboard
FLUENT_DASHBOARD_APP_ICONS = {
    'pyfb_company/customer': 'multiple25.png',
    'pyfb_endpoint/customerendpoint': 'users7.png',
    'pyfb_rating/customerratecard': 'music236.png',
    'pyfb_routing/customerroutinggroup': 'documents56.png',
    'pyfb_company/provider': 'network60.png',
    'pyfb_endpoint/providerendpoint': 'world90.png',
    'pyfb_rating/providerratecard': 'notebook24.png',
    'pyfb_routing/routinggroup': 'connectivity2.png',
    'pyfb_did/did': 'global45.png',
    'pyfb_company/companybalancehistory': 'shopping102.png',
    # ...
}

#FLUENT_DASHBOARD_ICON_THEME = 'flaticons'
FLUENT_DASHBOARD_DEFAULT_ICON = 'chat.png'
FLUENT_DASHBOARD_DEFAULT_MODULE = 'admin_tools.dashboard.modules.AppList'
FLUENT_DASHBOARD_APP_GROUPS = (
    (_('customer management'), {
        'models': (
            'pyfb_company.models.Customer',
            'pyfb_endpoint.models.CustomerEndpoint',
            'pyfb_rating.models.CustomerRatecard',
            'pyfb_routing.models.CustomerRoutingGroup',
        ),
        'collapsible': False,
        'deletable': False,
    }),
    (_('provider management'), {
        'models': (
            'pyfb_company.models.Provider',
            'pyfb_endpoint.models.ProviderEndpoint',
            'pyfb_rating.models.ProviderRatecard',
        ),
        'collapsible': False,
        'deletable': False,
    }),
    (_('rating and routing management'), {
        'models': (
            'pyfb_rating.models.CustomerRatecard',
            'pyfb_rating.models.ProviderRatecard',
            'pyfb_routing.models.RoutingGroup',
        ),
        'collapsible': False,
        'deletable': False,
    }),
    (_('did management'), {
        'models': (
            'pyfb_did.models.Did',
        ),
        'collapsible': False,
        'deletable': False,
    }),
    (_('finance'), {
        'models': (
            'pyfb_company.models.CompanyBalanceHistory',
        ),
        'collapsible': False,
        'deletable': False,
    }),
    (_('Applications'), {
        'models': ('*',),
        'module': FLUENT_DASHBOARD_DEFAULT_MODULE,
        'collapsible': True,
    }),
)
<commit_msg>Remove deprecated password hasher<commit_after>"""
Base settings to build other settings files upon.
"""
from django.utils.translation import ugettext_lazy as _

import environ

ROOT_DIR = environ.Path(__file__) - 3  # (pyfreebilling/config/settings/base.py - 3 = pyfreebilling/)
APPS_DIR = ROOT_DIR.path('pyfreebilling')

env = environ.Env()

READ_DOT_ENV_FILE = env.bool('DJANGO_READ_DOT_ENV_FILE', default=False)
if READ_DOT_ENV_FILE:
    # OS environment variables take precedence over variables from .env
    env.read_env(str(ROOT_DIR.path('.env')))

# GENERAL
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#debug
DEBUG = env.bool('DJANGO_DEBUG', False)
# Local time zone. Choices are
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# though not all of them may be available with every OS.
# In Windows, this must be set to your system time zone.
TIME_ZONE = 'UTC'
# https://docs.djangoproject.com/en/dev/ref/settings/#language-code
LANGUAGE_CODE = 'en-us'
# https://docs.djangoproject.com/en/dev/ref/settings/#site-id
SITE_ID = 1
# https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n
USE_I18N = True
# https://docs.djangoproject.com/en/dev/ref/settings/#use-l10n
USE_L10N = True
# https://docs.djangoproject.com/en/dev/ref/settings/#use-tz
USE_TZ = True

# DATABASES
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#databases
DATABASES = {
    'default': env.db('DATABASE_URL'),
}
DATABASES['default']['ATOMIC_REQUESTS'] = True

# URLS
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#root-urlconf
ROOT_URLCONF = 'config.urls'
# https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application
WSGI_APPLICATION = 'config.wsgi.application'

# APPS
# ------------------------------------------------------------------------------
DJANGO_APPS = [
    'fluent_dashboard',
    'admin_tools',     # for staticfiles in Django 1.3
    'admin_tools.theming',
    'admin_tools.menu',
    'admin_tools.dashboard',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 'django.contrib.humanize', # Handy template tags
    'django.contrib.admin',
]
THIRD_PARTY_APPS = [
    'django_extensions',
    'crispy_forms',
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    'rest_framework',
    'import_export',
    'django_countries',
    'constance',
    'migrate_sql',
]
PYFB_APPS = [
    'pyfb_kamailio',
    'pyfb_normalization',
    'pyfb_company',
    'pyfb_direction',
    'pyfb_endpoint',
    'pyfb_rating',
    'pyfb_routing',
    'pyfb_did',
    'pyfb_reporting',
]
LOCAL_APPS = [
    'pyfreebilling.users.apps.UsersAppConfig',
    # Your stuff: custom apps go here
]
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + PYFB_APPS + LOCAL_APPS

# MIGRATIONS
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#migration-modules
MIGRATION_MODULES = {
    'sites': 'pyfreebilling.contrib.sites.migrations'
}

# AUTHENTICATION
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#authentication-backends
AUTHENTICATION_BACKENDS = [
    'django.contrib.auth.backends.ModelBackend',
    'allauth.account.auth_backends.AuthenticationBackend',
]
# https://docs.djangoproject.com/en/dev/ref/settings/#auth-user-model
AUTH_USER_MODEL = 'users.User'
# https://docs.djangoproject.com/en/dev/ref/settings/#login-redirect-url
LOGIN_REDIRECT_URL = 'users:redirect'
# https://docs.djangoproject.com/en/dev/ref/settings/#login-url
LOGIN_URL = 'account_login'

# PASSWORDS
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers
PASSWORD_HASHERS = [
    # https://docs.djangoproject.com/en/dev/topics/auth/passwords/#using-argon2-with-django
    'django.contrib.auth.hashers.Argon2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
    'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]
# https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# MIDDLEWARE
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#middleware
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# STATIC
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#static-root
STATIC_ROOT = str(ROOT_DIR('staticfiles'))
# https://docs.djangoproject.com/en/dev/ref/settings/#static-url
STATIC_URL = '/static/'
# https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS
STATICFILES_DIRS = [
    str(APPS_DIR.path('static')),
]
# https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders
STATICFILES_FINDERS = [
    'django.contrib.staticfiles.finders.FileSystemFinder',
    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
]

# MEDIA
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#media-root
MEDIA_ROOT = str(APPS_DIR('media'))
# https://docs.djangoproject.com/en/dev/ref/settings/#media-url
MEDIA_URL = '/media/'

# TEMPLATES
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#templates
TEMPLATES = [
    {
        # https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATES-BACKEND
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        # https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs
        'DIRS': [
            str(APPS_DIR.path('templates')),
        ],
        'OPTIONS': {
            # https://docs.djangoproject.com/en/dev/ref/settings/#template-debug
            'debug': DEBUG,
            # https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders
            # https://docs.djangoproject.com/en/dev/ref/templates/api/#loader-types
            'loaders': [
                'admin_tools.template_loaders.Loader',
                'django.template.loaders.filesystem.Loader',
                'django.template.loaders.app_directories.Loader',
            ],
            # https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.template.context_processors.i18n',
                'django.template.context_processors.media',
                'django.template.context_processors.static',
                'django.template.context_processors.tz',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]
# http://django-crispy-forms.readthedocs.io/en/latest/install.html#template-packs
CRISPY_TEMPLATE_PACK = 'bootstrap4'

# FIXTURES
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#fixture-dirs
FIXTURE_DIRS = (
    str(APPS_DIR.path('fixtures')),
)

# SECURITY
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-httponly
SESSION_COOKIE_HTTPONLY = True
# https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-httponly
CSRF_COOKIE_HTTPONLY = True
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-browser-xss-filter
SECURE_BROWSER_XSS_FILTER = True
# https://docs.djangoproject.com/en/dev/ref/settings/#x-frame-options
X_FRAME_OPTIONS = 'DENY'

# EMAIL
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
EMAIL_BACKEND = env('DJANGO_EMAIL_BACKEND', default='django.core.mail.backends.smtp.EmailBackend')

# ADMIN
# ------------------------------------------------------------------------------
# Django Admin URL.
ADMIN_URL = 'admin/'
# https://docs.djangoproject.com/en/dev/ref/settings/#admins
ADMINS = [
    ("""Mathias WOLFF""", 'mathias@celea.org'),
]
# https://docs.djangoproject.com/en/dev/ref/settings/#managers
MANAGERS = ADMINS

# django-allauth
# ------------------------------------------------------------------------------
ACCOUNT_ALLOW_REGISTRATION = env.bool('DJANGO_ACCOUNT_ALLOW_REGISTRATION', True)
# https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_AUTHENTICATION_METHOD = 'username'
# https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_EMAIL_REQUIRED = True
# https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_EMAIL_VERIFICATION = 'mandatory'
# https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_ADAPTER = 'pyfreebilling.users.adapters.AccountAdapter'
# https://django-allauth.readthedocs.io/en/latest/configuration.html
SOCIALACCOUNT_ADAPTER = 'pyfreebilling.users.adapters.SocialAccountAdapter'

# Your stuff...
# ------------------------------------------------------------------------------
# Location of root django.contrib.admin URL
HONEYPOT_URL = r'admin/'
ADMIN_URL = r'extranet/'

# Settings in constance
CONSTANCE_CONFIG = {
    'THE_ANSWER': (42, 'Answer to the Ultimate Question of Life, '
                       'The Universe, and Everything'),
}

# ADMIN SETTINGS
ADMIN_SITE_NAME = 'PyFreeBilling'
ADMIN_SITE_TITLE = 'PyFreeBilling administration'
ADMIN_SITE_DESCRIPTION = 'Softswitch and billing application'
ADMIN_TOOLS_INDEX_DASHBOARD = 'fluent_dashboard.dashboard.FluentIndexDashboard'
ADMIN_TOOLS_APP_INDEX_DASHBOARD = 'fluent_dashboard.dashboard.FluentAppIndexDashboard'
ADMIN_TOOLS_MENU = 'config.menu.CustomMenu'
ADMIN_TOOLS_THEMING_CSS = 'css/theming.css'

# dashboard
FLUENT_DASHBOARD_APP_ICONS = {
    'pyfb_company/customer': 'multiple25.png',
    'pyfb_endpoint/customerendpoint': 'users7.png',
    'pyfb_rating/customerratecard': 'music236.png',
    'pyfb_routing/customerroutinggroup': 'documents56.png',
    'pyfb_company/provider': 'network60.png',
    'pyfb_endpoint/providerendpoint': 'world90.png',
    'pyfb_rating/providerratecard': 'notebook24.png',
    'pyfb_routing/routinggroup': 'connectivity2.png',
    'pyfb_did/did': 'global45.png',
    'pyfb_company/companybalancehistory': 'shopping102.png',
    # ...
}

#FLUENT_DASHBOARD_ICON_THEME = 'flaticons'
FLUENT_DASHBOARD_DEFAULT_ICON = 'chat.png'
FLUENT_DASHBOARD_DEFAULT_MODULE = 'admin_tools.dashboard.modules.AppList'
FLUENT_DASHBOARD_APP_GROUPS = (
    (_('customer management'), {
        'models': (
            'pyfb_company.models.Customer',
            'pyfb_endpoint.models.CustomerEndpoint',
            'pyfb_rating.models.CustomerRatecard',
            'pyfb_routing.models.CustomerRoutingGroup',
        ),
        'collapsible': False,
        'deletable': False,
    }),
    (_('provider management'), {
        'models': (
            'pyfb_company.models.Provider',
            'pyfb_endpoint.models.ProviderEndpoint',
            'pyfb_rating.models.ProviderRatecard',
        ),
        'collapsible': False,
        'deletable': False,
    }),
    (_('rating and routing management'), {
        'models': (
            'pyfb_rating.models.CustomerRatecard',
            'pyfb_rating.models.ProviderRatecard',
            'pyfb_routing.models.RoutingGroup',
        ),
        'collapsible': False,
        'deletable': False,
    }),
    (_('did management'), {
        'models': (
            'pyfb_did.models.Did',
        ),
        'collapsible': False,
        'deletable': False,
    }),
    (_('finance'), {
        'models': (
            'pyfb_company.models.CompanyBalanceHistory',
        ),
        'collapsible': False,
        'deletable': False,
    }),
    (_('Applications'), {
        'models': ('*',),
        'module': FLUENT_DASHBOARD_DEFAULT_MODULE,
        'collapsible': True,
    }),
)
<|endoftext|>


<commit_before># -*- coding: UTF-8 -*-
"""
Command Line Interface (CLI) for training a model.

Usage:
    trainer.py random-search [options]
    trainer.py [options]
    trainer.py -h | --help
    trainer.py -v | --version

Commands:
    random-search  Perform a random-search over hyper-parameter space.

Options:
    # universal options for training
    --train-data-path=<str>  Path to the training data file [default: ./data/name2proba_train.pkl]
    --model-dir=<str>  Path to the model directory [default: ./models]
    --batch-size=<int>  The number of samples per batch [default: 128]
    --patience=<int>  The number of iterations to keep training [default: 1024000]
    --valid-size=<float>  The proportion of dataset to use for validation [default: 0.1]
    --profile  Profile the training (profile_train/valid.json will be created)

    # options for when not doing random-search (ignored when doing random-search)
    --embed-size=<int>  The number of dimensions of the character embedding layer [default: 32]
    --char-rnn-size=<int>  The number of dimensions of the character-RNN layer [default: 128]
    --word-rnn-size=<int>  The number of dimensions of the word-RNN layer [default: 128]
    --learning-rate=<float>  Initial learning rate of SGD (Adam Optimizer) [default: 0.001]
    --embed-dropout=<float>  Dropout rate after the embedding layer [default: 0.]
    --char-rnn-dropout=<float>  Dropout rate after the character-RNN layer [default: 0.]
    --word-rnn-dropout=<float>  Dropout rate after the word-RNN layer [default: 0.]

    # universal non-training-related options
    -h --help  Show this screen
    -v --version  Show version
    --verbose  Show debug messages
    --log-path=<str>  Path to the log file [default: ./logs/trainer.log]

Examples:
    python trainer.py random-search
    python trainer.py --embed-size=128 --char-rnn-size=128 --word-rnn-size=128 --char-rnn-dropout=0.5

"""
import json
import os
import pickle
import logging
from collections import OrderedDict
from random import choice

import numpy as np
from docopt import docopt
from sklearn.model_selection import train_test_split
from tensorflow.python.framework.errors_impl import InvalidArgumentError

from chicksexer.util import set_default_log_level, set_log_level, get_logger, \
    set_default_log_path, set_log_path
from chicksexer import __version__
from chicksexer.classifier import CharLSTM, set_log_path as set_classifier_log_path

_RANDOM_STATE = 0  # this is to make train/test split always return the same split
_HOME_DIR = '~/'

_LOGGER = get_logger(__name__)

__author__ = 'kensk8er'

def _get_parameter_space():
    """Define the parameter space to explore here."""
    parameter_space = OrderedDict()
    parameter_space.update({'embedding_size': [16 * i for i in range(1, 9)]})
    parameter_space.update({'char_rnn_size': [128 * i for i in range(1, 11)]})
    parameter_space.update({'word_rnn_size': [128 * i for i in range(1, 11)]})
    parameter_space.update({'learning_rate': [0.0001 * (2 ** i) for i in range(8)]})
    parameter_space.update({'embedding_dropout': [0., 0.01, 0.03, 0.05, 0.1]})
    parameter_space.update({'char_rnn_dropout': [0., 0.01, 0.05, 0.1, 0.15, 0.2, 0.3, 0.4, 0.5]})
    parameter_space.update({'word_rnn_dropout': [0., 0.01, 0.05, 0.1, 0.15, 0.2, 0.3, 0.4, 0.5]})
    return parameter_space

def _expand_user_path(args):
    """Expand to absolute path when ~/ appears in the path."""
    for arg_key, arg_val in args.items():
        if isinstance(arg_val, str) and arg_val.startswith(_HOME_DIR):
            args[arg_key] = os.path.expanduser(arg_val)
    return args

def _get_training_data(train_data_path):
    """Load training data from the file and return them."""
    names = list()
    y = list()

    _LOGGER.debug('Loading training data...')
    with open(train_data_path, 'rb') as pickle_file:
        name2proba = pickle.load(pickle_file)

    for name, proba in name2proba.items():
        names.append(name)
        y.append(proba)

    return names, y

def _construct_model_name(parameters):
    """Construct model name from the parameters and return it."""
    def format_precision(number):
        if isinstance(number, float):
            return '{:.5f}'.format(number).rstrip('0')
        else:
            return str(number)

    def format_val(val):
        if isinstance(val, list):
            return '-'.join(format_precision(ele) for ele in val)
        else:
            return format_precision(val)

    parameter_names = list(parameters.keys())
    parameter_names.sort()  # sort by key in order to have consistency in the order of params
    return '_'.join('{}-{}'.format(key, format_val(parameters[key])) for key in parameter_names)

def _random_search(names_train, names_valid, y_train, y_valid, parameter_space, args):
    """Perform random search over given hyper-parameter space."""

    def sample_parameters(parameter_space):
        """Sample parameters from the parameter space."""
        parameters = OrderedDict()
        for key, vals in parameter_space.items():
            parameters[key] = choice(vals)  # sample a value randomly
        return parameters

    searched_parameters = set()
    best_valid_score = np.float64('-inf')
    best_parameters = None
    count = 1

    try:
        while True:
            parameters = sample_parameters(parameter_space)
            if str(parameters) in searched_parameters:
                continue

            _LOGGER.info('---------- ({}) Start experimenting with a new parameter set ----------\n'
                         .format(count))
            _LOGGER.info('Hyper-parameters:\n{}'.format(json.dumps(parameters, indent=2)))

            # construct the model name
            model_name = _construct_model_name(parameters)
            model_path = os.path.join(args['--model-dir'], model_name)
            _LOGGER.info('Model name: {}'.format(model_name))

            _LOGGER.info('Initialize CharLSTM object with the new parameters...')
            model = CharLSTM(**parameters)

            _LOGGER.info('Started the train() method...')
            score = model.train(names_train, y_train, names_valid, y_valid, model_path,
                                int(args['--batch-size']), int(args['--patience']))
            searched_parameters.add(str(parameters))

            if score > best_valid_score:
                _LOGGER.info('Achieved best validation score so far in the search.')
                _LOGGER.info('Hyper-parameters:\n{}'.format(json.dumps(parameters, indent=2)))
                best_valid_score = score
                best_parameters = parameters

            _LOGGER.info('-------- ({}) Finished experimenting with the parameter set --------\n\n'
                         .format(count))
            count += 1

    except KeyboardInterrupt:
        _LOGGER.info('Random Search finishes because of Keyboard Interrupt.')
        _LOGGER.info('Best Validation Score: {:.3f}'.format(best_valid_score))
        _LOGGER.info('Best Hyper-parameters:\n{}'.format(json.dumps(best_parameters, indent=2)))

    except InvalidArgumentError as error:
        _LOGGER.exception(error)
        _LOGGER.info('-------- ({}) Skip the parameter set --------\n\n'.format(count))

def _simple_train(names_train, names_valid, y_train, y_valid, args):
    """Simply train a model using hyper-parameters specified in the args."""
    parameters = {
        'embedding_size': int(args['--embed-size']),
        'char_rnn_size': int(args['--char-rnn-size']),
        'word_rnn_size': int(args['--word-rnn-size']),
        'learning_rate': float(args['--learning-rate']),
        'embedding_dropout': float(args['--embed-dropout']),
        'char_rnn_dropout': float(args['--char-rnn-dropout']),
        'word_rnn_dropout': float(args['--word-rnn-dropout']),
    }
    model_name = _construct_model_name(parameters)
    model_path = os.path.join(args['--model-dir'], model_name)

    _LOGGER.info('Initialize CharLSTM object with the new parameters...')
    model = CharLSTM(**parameters)

    _LOGGER.info('Started the train() method...')
    model.train(names_train, y_train, names_valid, y_valid, model_path, int(args['--batch-size']),
                int(args['--patience']))

def main():
    """CLI for performing model training."""
    args = docopt(__doc__, version=__version__)
    args = _expand_user_path(args)

    if args['--verbose']:
        set_default_log_level(logging.DEBUG)
        set_log_level(_LOGGER, logging.DEBUG)

    if args['--log-path']:
        log_path = args['--log-path']
        set_default_log_path(log_path)
        set_log_path(_LOGGER, log_path)
        set_classifier_log_path(log_path)

    _LOGGER.debug('Configuration:\n{}'.format(args))

    names, y = _get_training_data(args['--train-data-path'])

    # split into train/valid set
    names_train, names_valid, y_train, y_valid = train_test_split(
        names, y, random_state=_RANDOM_STATE, test_size=float(args['--valid-size']))

    if args['random-search']:
        _random_search(
            names_train, names_valid, y_train, y_valid, _get_parameter_space(), args)
    else:
        _simple_train(names_train, names_valid, y_train, y_valid, args)

if __name__ == '__main__':
    main()
<commit_msg>Updated search parameters.<commit_after># -*- coding: UTF-8 -*-
"""
Command Line Interface (CLI) for training a model.

Usage:
    trainer.py random-search [options]
    trainer.py [options]
    trainer.py -h | --help
    trainer.py -v | --version

Commands:
    random-search  Perform a random-search over hyper-parameter space.

Options:
    # universal options for training
    --train-data-path=<str>  Path to the training data file [default: ./data/name2proba_train.pkl]
    --model-dir=<str>  Path to the model directory [default: ./models]
    --batch-size=<int>  The number of samples per batch [default: 128]
    --patience=<int>  The number of iterations to keep training [default: 1024000]
    --valid-size=<float>  The proportion of dataset to use for validation [default: 0.1]
    --profile  Profile the training (profile_train/valid.json will be created)

    # options for when not doing random-search (ignored when doing random-search)
    --embed-size=<int>  The number of dimensions of the character embedding layer [default: 32]
    --char-rnn-size=<int>  The number of dimensions of the character-RNN layer [default: 128]
    --word-rnn-size=<int>  The number of dimensions of the word-RNN layer [default: 128]
    --learning-rate=<float>  Initial learning rate of SGD (Adam Optimizer) [default: 0.001]
    --embed-dropout=<float>  Dropout rate after the embedding layer [default: 0.]
    --char-rnn-dropout=<float>  Dropout rate after the character-RNN layer [default: 0.]
    --word-rnn-dropout=<float>  Dropout rate after the word-RNN layer [default: 0.]

    # universal non-training-related options
    -h --help  Show this screen
    -v --version  Show version
    --verbose  Show debug messages
    --log-path=<str>  Path to the log file [default: ./logs/trainer.log]

Examples:
    python trainer.py random-search
    python trainer.py --embed-size=128 --char-rnn-size=128 --word-rnn-size=128 --char-rnn-dropout=0.5

"""
import json
import os
import pickle
import logging
from collections import OrderedDict
from random import choice

import numpy as np
from docopt import docopt
from sklearn.model_selection import train_test_split
from tensorflow.python.framework.errors_impl import InvalidArgumentError

from chicksexer.util import set_default_log_level, set_log_level, get_logger, \
    set_default_log_path, set_log_path
from chicksexer import __version__
from chicksexer.classifier import CharLSTM, set_log_path as set_classifier_log_path

_RANDOM_STATE = 0  # this is to make train/test split always return the same split
_HOME_DIR = '~/'

_LOGGER = get_logger(__name__)

__author__ = 'kensk8er'

def _get_parameter_space():
    """Define the parameter space to explore here."""
    parameter_space = OrderedDict()
    parameter_space.update({'embedding_size': [16 * i for i in range(1, 5)]})
    parameter_space.update({'char_rnn_size': [64 * i for i in range(1, 5)]})
    parameter_space.update({'word_rnn_size': [64 * i for i in range(1, 5)]})
    parameter_space.update({'learning_rate': [0.0001 * (2 ** i) for i in range(8)]})
    parameter_space.update({'embedding_dropout': [0., 0.01, 0.03, 0.05, 0.1]})
    parameter_space.update({'char_rnn_dropout': [0., 0.01, 0.05, 0.1, 0.15, 0.2, 0.3, 0.4, 0.5]})
    parameter_space.update({'word_rnn_dropout': [0., 0.01, 0.05, 0.1, 0.15, 0.2, 0.3, 0.4, 0.5]})
    return parameter_space

def _expand_user_path(args):
    """Expand to absolute path when ~/ appears in the path."""
    for arg_key, arg_val in args.items():
        if isinstance(arg_val, str) and arg_val.startswith(_HOME_DIR):
            args[arg_key] = os.path.expanduser(arg_val)
    return args

def _get_training_data(train_data_path):
    """Load training data from the file and return them."""
    names = list()
    y = list()

    _LOGGER.debug('Loading training data...')
    with open(train_data_path, 'rb') as pickle_file:
        name2proba = pickle.load(pickle_file)

    for name, proba in name2proba.items():
        names.append(name)
        y.append(proba)

    return names, y

def _construct_model_name(parameters):
    """Construct model name from the parameters and return it."""
    def format_precision(number):
        if isinstance(number, float):
            return '{:.5f}'.format(number).rstrip('0')
        else:
            return str(number)

    def format_val(val):
        if isinstance(val, list):
            return '-'.join(format_precision(ele) for ele in val)
        else:
            return format_precision(val)

    parameter_names = list(parameters.keys())
    parameter_names.sort()  # sort by key in order to have consistency in the order of params
    return '_'.join('{}-{}'.format(key, format_val(parameters[key])) for key in parameter_names)

def _random_search(names_train, names_valid, y_train, y_valid, parameter_space, args):
    """Perform random search over given hyper-parameter space."""

    def sample_parameters(parameter_space):
        """Sample parameters from the parameter space."""
        parameters = OrderedDict()
        for key, vals in parameter_space.items():
            parameters[key] = choice(vals)  # sample a value randomly
        return parameters

    searched_parameters = set()
    best_valid_score = np.float64('-inf')
    best_parameters = None
    count = 1

    try:
        while True:
            parameters = sample_parameters(parameter_space)
            if str(parameters) in searched_parameters:
                continue

            _LOGGER.info('---------- ({}) Start experimenting with a new parameter set ----------\n'
                         .format(count))
            _LOGGER.info('Hyper-parameters:\n{}'.format(json.dumps(parameters, indent=2)))

            # construct the model name
            model_name = _construct_model_name(parameters)
            model_path = os.path.join(args['--model-dir'], model_name)
            _LOGGER.info('Model name: {}'.format(model_name))

            _LOGGER.info('Initialize CharLSTM object with the new parameters...')
            model = CharLSTM(**parameters)

            _LOGGER.info('Started the train() method...')
            score = model.train(names_train, y_train, names_valid, y_valid, model_path,
                                int(args['--batch-size']), int(args['--patience']))
            searched_parameters.add(str(parameters))

            if score > best_valid_score:
                _LOGGER.info('Achieved best validation score so far in the search.')
                _LOGGER.info('Hyper-parameters:\n{}'.format(json.dumps(parameters, indent=2)))
                best_valid_score = score
                best_parameters = parameters

            _LOGGER.info('-------- ({}) Finished experimenting with the parameter set --------\n\n'
                         .format(count))
            count += 1

    except KeyboardInterrupt:
        _LOGGER.info('Random Search finishes because of Keyboard Interrupt.')
        _LOGGER.info('Best Validation Score: {:.3f}'.format(best_valid_score))
        _LOGGER.info('Best Hyper-parameters:\n{}'.format(json.dumps(best_parameters, indent=2)))

    except InvalidArgumentError as error:
        _LOGGER.exception(error)
        _LOGGER.info('-------- ({}) Skip the parameter set --------\n\n'.format(count))

def _simple_train(names_train, names_valid, y_train, y_valid, args):
    """Simply train a model using hyper-parameters specified in the args."""
    parameters = {
        'embedding_size': int(args['--embed-size']),
        'char_rnn_size': int(args['--char-rnn-size']),
        'word_rnn_size': int(args['--word-rnn-size']),
        'learning_rate': float(args['--learning-rate']),
        'embedding_dropout': float(args['--embed-dropout']),
        'char_rnn_dropout': float(args['--char-rnn-dropout']),
        'word_rnn_dropout': float(args['--word-rnn-dropout']),
    }
    model_name = _construct_model_name(parameters)
    model_path = os.path.join(args['--model-dir'], model_name)

    _LOGGER.info('Initialize CharLSTM object with the new parameters...')
    model = CharLSTM(**parameters)

    _LOGGER.info('Started the train() method...')
    model.train(names_train, y_train, names_valid, y_valid, model_path, int(args['--batch-size']),
                int(args['--patience']))

def main():
    """CLI for performing model training."""
    args = docopt(__doc__, version=__version__)
    args = _expand_user_path(args)

    if args['--verbose']:
        set_default_log_level(logging.DEBUG)
        set_log_level(_LOGGER, logging.DEBUG)

    if args['--log-path']:
        log_path = args['--log-path']
        set_default_log_path(log_path)
        set_log_path(_LOGGER, log_path)
        set_classifier_log_path(log_path)

    _LOGGER.debug('Configuration:\n{}'.format(args))

    names, y = _get_training_data(args['--train-data-path'])

    # split into train/valid set
    names_train, names_valid, y_train, y_valid = train_test_split(
        names, y, random_state=_RANDOM_STATE, test_size=float(args['--valid-size']))

    if args['random-search']:
        _random_search(
            names_train, names_valid, y_train, y_valid, _get_parameter_space(), args)
    else:
        _simple_train(names_train, names_valid, y_train, y_valid, args)

if __name__ == '__main__':
    main()
<|endoftext|>


<commit_before>from base64 import b64encode
from datetime import datetime, timedelta
from subprocess import Popen, PIPE
import logging
import random

from django.conf import settings
from django.db import models
from django.utils.translation import ugettext as _
from lxml import etree
from lxml.builder import E
from suds import Client

logger = logging.getLogger(__name__)

endpoints = {}
if settings.AFIP_DEBUG:
    endpoints['wsaa'] = "https://wsaahomo.afip.gov.ar/ws/services/LoginCms?wsdl"  # NOQA
    endpoints['wsfe'] = "https://wswhomo.afip.gov.ar/wsfev1/service.asmx?WSDL"
else:
    endpoints['wsaa'] = "https://wsaa.afip.gov.ar/ws/services/LoginCms?wsdl"
    endpoints['wsfe'] = "https://servicios1.afip.gov.ar/wsfev1/service.asmx?WSDL"  # NOQA

def format_date(date):
    """
    "Another date formatting function?" you're thinking, eh? Well, this
    actually formats dates in the *exact* format the AFIP's WS expects it,
    which is almost like ISO8601.

    Note that .isoformat() works fine on PROD, but not on TESTING.
    """
    return date.strftime("%Y-%m-%dT%H:%M:%S-00:00")

class GenericAfipTypeManager(models.Manager):

    def __init__(self, service_name):
        super().__init__()
        self.__service_name = service_name

    def populate(self):
        """
        Populates the database with valid types retrieved from AFIP's
        webservices.
        """
        pass  # TODO: Not Implemented!

class GenericAfipType(models.Model):
    code = models.CharField(
        _('code'),
        max_length=3,
    )
    description = models.CharField(
        _('description'),
        max_length=250,
    )
    valid_from = models.DateField(
        _('valid from'),
    )
    valid_to = models.DateField(
        _('valid until'),
    )

    def __str__(self):
        return self.description

    class Meta:
        abstract = True

class ReceiptType(GenericAfipType):

    objects = GenericAfipTypeManager('FEParamGetTiposCbte')

    class Meta:
        verbose_name = _("receipt type")
        verbose_name_plural = _("receipt types")

class ConceptType(GenericAfipType):

    objects = GenericAfipTypeManager('FEParamGetTiposConcepto')

    class Meta:
        verbose_name = _("concept type")
        verbose_name_plural = _("concept types")

class DocumentType(GenericAfipType):

    objects = GenericAfipTypeManager('FEParamGetTiposDoc')

    class Meta:
        verbose_name = _("document type")
        verbose_name_plural = _("document types")

class VatType(GenericAfipType):

    objects = GenericAfipTypeManager('FEParamGetTiposIva')

    class Meta:
        verbose_name = _("vat type")
        verbose_name_plural = _("vat types")

class TaxType(GenericAfipType):

    objects = GenericAfipTypeManager('FEParamGetTiposTributos')

    class Meta:
        verbose_name = _("tax type")
        verbose_name_plural = _("tax types")

class CurrencyType(GenericAfipType):

    objects = GenericAfipTypeManager('FEParamGetTiposTiposMonedas')

    def __str__(self):
        return self.code

    class Meta:
        verbose_name = _("currency type")
        verbose_name_plural = _("currency types")

class TaxPayer(models.Model):
    name = models.CharField(
        _('name'),
        max_length=32,
        help_text=_('A friendly name to recognize this taxpayer.'),
    )
    key = models.FileField(
        _('key'),
        null=True,
    )
    certificate = models.FileField(
        _('certificate'),
        null=True,
    )
    cuit = models.BigIntegerField(
        _('cuit'),
    )

    def create_ticket(self, service):
        return AuthTicket(owner=self, service=service)

    def __str__(self):
        return str(self.cuit)

    class Meta:
        verbose_name = _("taxpayer")
        verbose_name_plural = _("taxpayers")

class PointOfSales(models.Model):
    number = models.PositiveSmallIntegerField(
        _('number'),
    )
    issuance_type = models.CharField(
        _('issuance type'),
        max_length=8,
        help_text='Indicates if thie POS emits using CAE and CAEA.'
    )
    blocked = models.BooleanField(
        _('blocked'),
    )
    drop_date = models.DateField(
        _('drop date'),
        null=True,
        blank=True,
    )

    owner = models.ForeignKey(
        TaxPayer,
        verbose_name=_('owner'),
    )

    def __str__(self):
        return str(self.number)

    class Meta:
        verbose_name = _('point of sales')
        verbose_name_plural = _('points of sales')

class AuthTicket(models.Model):
    owner = models.ForeignKey(
        TaxPayer,
        verbose_name=_('owner'),
    )
    unique_id = models.IntegerField(
        _('unique id'),
    )
    generated = models.DateTimeField(
        _('generated'),
    )
    expires = models.DateTimeField(
        _('expires'),
    )
    service = models.CharField(
        _('service'),
        max_length=6,
        help_text=_('Service for which this ticket has been authorized'),
    )

    token = models.TextField(
        _('token'),
    )
    signature = models.TextField(
        _('signature'),
    )

    TOKEN_XPATH = "/loginTicketResponse/credentials/token"
    SIGN_XPATH = "/loginTicketResponse/credentials/sign"

    def __create_request_xml(self):
        request_xml = (
            E.loginTicketRequest(
                {'version': '1.0'},
                E.header(
                    E.uniqueId(str(self.unique_id)),
                    E.generationTime(self.generated),
                    E.expirationTime(self.expires),
                ),
                E.service(self.service)
            )
        )
        return etree.tostring(request_xml, pretty_print=True)

    def __sign_request(self, request):
        cert = self.owner.certificate.file.name
        key = self.owner.key.file.name

        return Popen(
            [
                "openssl", "smime", "-sign", "-signer", cert, "-inkey", key,
                "-outform", "DER", "-nodetach"
            ],
            stdin=PIPE, stdout=PIPE, stderr=PIPE
        ).communicate(request)[0]

    def __create_request(self):
        now = datetime.now()  # up to 24h old
        tomorrow = now + timedelta(hours=10)  # up to 24h in the future

        self.generated = format_date(now)
        self.expires = format_date(tomorrow)
        # Can be larger, but let's not waste on this:
        self.unique_id = random.randint(0, 2147483647)

        request = self.__create_request_xml()
        signed_request = self.__sign_request(request)
        return b64encode(signed_request).decode()

    def authenticate(self, save=True):
        client = Client(endpoints['wsaa'])
        request = self.__create_request()
        raw_response = client.service.loginCms(request)
        response = etree.fromstring(raw_response.encode('utf-8'))

        self.token = response.xpath(self.TOKEN_XPATH)[0].text
        self.signature = response.xpath(self.SIGN_XPATH)[0].text
        if save:
            self.save()

    def __str__(self):
        return str(self.unique_id)

    class Meta:
        verbose_name = _('authorization ticket')
        verbose_name = _('authorization tickets')

class ReceiptBatch(models.Model):
    """
    Receipts are validated sent in batches.
    """

    receipt_type = models.ForeignKey(
        ReceiptType,
        verbose_name=_('receipt type'),
    )
    point_of_sales = models.ForeignKey(
        PointOfSales,
        verbose_name=_('point of sales'),
    )

    def __str__(self):
        return str(self.id)

    class Meta:
        verbose_name = _('receipt batch')
        verbose_name = _('receipt batches')

class Receipt(models.Model):
    """
    A receipt, as sent to AFIP.

    Note that AFIP allows sending ranges of receipts, but this isn't generally
    what you want, so we model invoices individually.

    You'll probably want to relate some `Sale` or `Order` object from your
    model with each Receipt.
    """
    batch = models.ForeignKey(
        ReceiptBatch,
        related_name='receipts',
        null=True,
        blank=True,
        verbose_name=_('receipt batch'),
        help_text=_(
            'Receipts are validated in batches, so it must be assigned one '
            'before validation is possible.'),
    )
    concept = models.ForeignKey(
        ConceptType,
        verbose_name=_('concept'),
        related_name='receipts',
    )
    document_type = models.ForeignKey(
        DocumentType,
        verbose_name=_('document type'),
        related_name='receipts',
        help_text=_(
            'The document type of the customer to whom this receipt '
            'is addressed'
        ),
    )
    document_number = models.BigIntegerField(
        _('document number'),
        help_text=_(
            'The document number of the customer to whom this receipt '
            'is addressed'
        )
    )
    # NOTE: WS will expect receipt_from and receipt_to.
    receipt_number = models.PositiveIntegerField(
        _('receipt number'),
        null=True,
    )
    issued_date = models.DateField(
        verbose_name=_('issued date'),
        help_text=_('Can diverge up to 5 days for good, or 10 days otherwise'),
    )
    total_amount = models.DecimalField(
        # ImpTotal
        _('total amount'),
        max_digits=15,
        decimal_places=2,
        help_text=_(
            'Must be equal to untaxed amount + exempt amount + taxes + vat.'
        )
    )
    net_untaxed = models.DecimalField(
        # ImpTotConc
        _('total untaxable amount'),
        max_digits=15,
        decimal_places=2,
        help_text=_(
            'The total amount to which taxes do not apply. '
            'For C-type receipts, this must be zero.'
        ),
    )
    net_taxed = models.DecimalField(
        # ImpNeto
        _('total taxable amount'),
        max_digits=15,
        decimal_places=2,
        help_text=_(
            'The total amount to which taxes apply. '
            'For C-type receipts, this is equal to the subtotal.'
            ),
    )
    exempt_amount = models.DecimalField(
        # ImpOpEx
        # Sólo para emisores que son IVA exento
        _('exempt amount'),
        max_digits=15,
        decimal_places=2,
        help_text=_(
            'Only for categories which are tax-exempt. '
            'For C-type receipts, this must be zero.'
        ),
    )
    vat_amount = models.DecimalField(
        _('vat amount'),
        max_digits=15,
        decimal_places=2,
        help_text=_('Must be equal to the sum of all Vat objects.'),
    )
    tax_amount = models.DecimalField(
        _('tax amount'),
        max_digits=15,
        decimal_places=2,
        help_text=_('Must be equal to the sum of all Tax objects.'),
    )
    service_start = models.DateField(
        _('service start date'),
        help_text=_(
            'Date on which a service started. No applicable for goods.'
        ),
        null=True,
        blank=True,
    )
    service_end = models.DateField(
        _('service end date'),
        help_text=_(
            'Date on which a service ended. No applicable for goods.'
        ),
        null=True,
        blank=True,
    )
    expiration_date = models.DateField(
        _('receipt expiration date'),
        help_text=_(
            'Date on which this receipt expires. No applicable for goods.'
        ),
        null=True,
        blank=True,
    )
    currency = models.ForeignKey(
        CurrencyType,
        verbose_name=_('currency'),
        related_name='documents',
        help_text=_(
            'Currency in which this receipt is issued.',
        ),
    )
    currency_quote = models.DecimalField(
        _('currency quote'),
        max_digits=10,
        decimal_places=6,
        help_text=_(
            'Quote of the day for the currency used in the receipt',
        ),
    )
    related_receipts = models.ManyToManyField(
        'Receipt',
        _('related receipts'),
        blank=True,
    )

    # Not implemented: optionals

    # TODO: methods to compute total, vat_amount and taxes_amount

    # These two values are stored in the receipt's batch. However, before the
    # receipt is assigned into a batch, this value should be used.
    receipt_type = models.ForeignKey(ReceiptType)
    point_of_sales = models.ForeignKey(PointOfSales)

    def __str__(self):
        return '{} #{}'.format(self.receipt_type, self.receipt_number)

    class Meta:
        verbose_name = _('receipt')
        verbose_name_plural = _('receipts')
        unique_together = (
            ('receipt_type', 'receipt_number',)
        )
        # TODO: index_together...

class Tax(models.Model):
    tax_type = models.ForeignKey(
        TaxType,
        verbose_name=_('tax type'),
    )
    description = models.CharField(
        _('description'),
        max_length=80,
    )
    base_amount = models.DecimalField(
        _('base amount'),
        max_digits=15,
        decimal_places=2,
    )
    aliquot = models.DecimalField(
        _('aliquot'),
        max_digits=5,
        decimal_places=2,
    )
    amount = models.DecimalField(
        _('amount'),
        max_digits=15,
        decimal_places=2,
    )

    receipt = models.ForeignKey(Receipt)

    class Meta:
        verbose_name = _('tax')
        verbose_name_plural = _('taxes')

class Vat(models.Model):
    vat_type = models.ForeignKey(
        VatType,
        verbose_name=_('vat type'),
    )
    base = models.DecimalField(
        _('base amount'),
        max_digits=15,
        decimal_places=2,
    )
    amount = models.DecimalField(
        _('amount'),
        max_digits=15,
        decimal_places=2,
    )

    receipt = models.ForeignKey(Receipt)

    class Meta:
        verbose_name = _('vat')
        verbose_name_plural = _('vat')

class Validation(models.Model):
    RESULT_APPROVED = 'A'
    RESULT_REJECTED = 'R'
    RESULT_PARTIAL = 'P'

    processed_date = models.DateField(
        _('processed date'),
    )
    result = models.CharField(
        _('result'),
        max_length=1,
        choices=(
            (RESULT_APPROVED, _('approved')),
            (RESULT_REJECTED, _('rejected')),
            (RESULT_PARTIAL, _('partial')),
        ),
    )

    batch = models.ForeignKey(
        ReceiptBatch,
        related_name='validation',
        verbose_name=_('receipt batch'),
    )

    class Meta:
        verbose_name = _('validation')
        verbose_name_plural = _('validations')

class Observation(models.Model):
    code = models.PositiveSmallIntegerField(
        _('code'),
    )
    message = models.CharField(
        _('message'),
        max_length=255,
    )

    class Meta:
        verbose_name = _('observation')
        verbose_name_plural = _('observations')

class ReceiptValidation(models.Model):
    validation = models.ForeignKey(
        Validation,
        verbose_name=_('validation'),
        related_name='receipts',
    )
    result = models.CharField(
        _('result'),
        max_length=1,
        choices=(
            (Validation.RESULT_APPROVED, _('approved')),
            (Validation.RESULT_REJECTED, _('rejected')),
            (Validation.RESULT_PARTIAL, _('partial')),
        ),
    )
    cae = models.CharField(
        _('cae'),
        max_length=14
    )
    cae_expiration = models.DateTimeField(
        _('cae expiration'),
    )
    observations = models.ForeignKey(
        Observation,
        verbose_name=_('observations'),
    )

    receipt = models.OneToOneField(
        Receipt,
        related_name='validation',
        verbose_name=_('receipt'),
    )

    class Meta:
        verbose_name = _('receipt validation')
        verbose_name_plural = _('receipt validations')
<commit_msg>Split service data from models. Lazy-init them.<commit_after>from base64 import b64encode
from datetime import datetime, timedelta
from subprocess import Popen, PIPE
import logging
import random

from django.db import models
from django.utils.translation import ugettext as _
from lxml import etree
from lxml.builder import E

from .utils import format_date, wsaa_client

logger = logging.getLogger(__name__)

class GenericAfipTypeManager(models.Manager):

    def __init__(self, service_name):
        super().__init__()
        self.__service_name = service_name

    def populate(self):
        """
        Populates the database with valid types retrieved from AFIP's
        webservices.
        """
        pass  # TODO: Not Implemented!

class GenericAfipType(models.Model):
    code = models.CharField(
        _('code'),
        max_length=3,
    )
    description = models.CharField(
        _('description'),
        max_length=250,
    )
    valid_from = models.DateField(
        _('valid from'),
    )
    valid_to = models.DateField(
        _('valid until'),
    )

    def __str__(self):
        return self.description

    class Meta:
        abstract = True

class ReceiptType(GenericAfipType):

    objects = GenericAfipTypeManager('FEParamGetTiposCbte')

    class Meta:
        verbose_name = _("receipt type")
        verbose_name_plural = _("receipt types")

class ConceptType(GenericAfipType):

    objects = GenericAfipTypeManager('FEParamGetTiposConcepto')

    class Meta:
        verbose_name = _("concept type")
        verbose_name_plural = _("concept types")

class DocumentType(GenericAfipType):

    objects = GenericAfipTypeManager('FEParamGetTiposDoc')

    class Meta:
        verbose_name = _("document type")
        verbose_name_plural = _("document types")

class VatType(GenericAfipType):

    objects = GenericAfipTypeManager('FEParamGetTiposIva')

    class Meta:
        verbose_name = _("vat type")
        verbose_name_plural = _("vat types")

class TaxType(GenericAfipType):

    objects = GenericAfipTypeManager('FEParamGetTiposTributos')

    class Meta:
        verbose_name = _("tax type")
        verbose_name_plural = _("tax types")

class CurrencyType(GenericAfipType):

    objects = GenericAfipTypeManager('FEParamGetTiposTiposMonedas')

    def __str__(self):
        return self.code

    class Meta:
        verbose_name = _("currency type")
        verbose_name_plural = _("currency types")

class TaxPayer(models.Model):
    name = models.CharField(
        _('name'),
        max_length=32,
        help_text=_('A friendly name to recognize this taxpayer.'),
    )
    key = models.FileField(
        _('key'),
        null=True,
    )
    certificate = models.FileField(
        _('certificate'),
        null=True,
    )
    cuit = models.BigIntegerField(
        _('cuit'),
    )

    def create_ticket(self, service):
        return AuthTicket(owner=self, service=service)

    def __str__(self):
        return str(self.cuit)

    class Meta:
        verbose_name = _("taxpayer")
        verbose_name_plural = _("taxpayers")

class PointOfSales(models.Model):
    number = models.PositiveSmallIntegerField(
        _('number'),
    )
    issuance_type = models.CharField(
        _('issuance type'),
        max_length=8,
        help_text='Indicates if thie POS emits using CAE and CAEA.'
    )
    blocked = models.BooleanField(
        _('blocked'),
    )
    drop_date = models.DateField(
        _('drop date'),
        null=True,
        blank=True,
    )

    owner = models.ForeignKey(
        TaxPayer,
        verbose_name=_('owner'),
    )

    def __str__(self):
        return str(self.number)

    class Meta:
        verbose_name = _('point of sales')
        verbose_name_plural = _('points of sales')

class AuthTicket(models.Model):
    owner = models.ForeignKey(
        TaxPayer,
        verbose_name=_('owner'),
    )
    unique_id = models.IntegerField(
        _('unique id'),
    )
    generated = models.DateTimeField(
        _('generated'),
    )
    expires = models.DateTimeField(
        _('expires'),
    )
    service = models.CharField(
        _('service'),
        max_length=6,
        help_text=_('Service for which this ticket has been authorized'),
    )

    token = models.TextField(
        _('token'),
    )
    signature = models.TextField(
        _('signature'),
    )

    TOKEN_XPATH = "/loginTicketResponse/credentials/token"
    SIGN_XPATH = "/loginTicketResponse/credentials/sign"

    def __create_request_xml(self):
        request_xml = (
            E.loginTicketRequest(
                {'version': '1.0'},
                E.header(
                    E.uniqueId(str(self.unique_id)),
                    E.generationTime(self.generated),
                    E.expirationTime(self.expires),
                ),
                E.service(self.service)
            )
        )
        return etree.tostring(request_xml, pretty_print=True)

    def __sign_request(self, request):
        cert = self.owner.certificate.file.name
        key = self.owner.key.file.name

        return Popen(
            [
                "openssl", "smime", "-sign", "-signer", cert, "-inkey", key,
                "-outform", "DER", "-nodetach"
            ],
            stdin=PIPE, stdout=PIPE, stderr=PIPE
        ).communicate(request)[0]

    def __create_request(self):
        now = datetime.now()  # up to 24h old
        tomorrow = now + timedelta(hours=10)  # up to 24h in the future

        self.generated = format_date(now)
        self.expires = format_date(tomorrow)
        # Can be larger, but let's not waste on this:
        self.unique_id = random.randint(0, 2147483647)

        request = self.__create_request_xml()
        signed_request = self.__sign_request(request)
        return b64encode(signed_request).decode()

    def authorize(self, save=True):
        request = self.__create_request()
        raw_response = wsaa_client.service.loginCms(request)
        response = etree.fromstring(raw_response.encode('utf-8'))

        self.token = response.xpath(self.TOKEN_XPATH)[0].text
        self.signature = response.xpath(self.SIGN_XPATH)[0].text
        if save:
            self.save()

    def __str__(self):
        return str(self.unique_id)

    class Meta:
        verbose_name = _('authorization ticket')
        verbose_name = _('authorization tickets')

class ReceiptBatch(models.Model):
    """
    Receipts are validated sent in batches.
    """

    receipt_type = models.ForeignKey(
        ReceiptType,
        verbose_name=_('receipt type'),
    )
    point_of_sales = models.ForeignKey(
        PointOfSales,
        verbose_name=_('point of sales'),
    )

    def __str__(self):
        return str(self.id)

    class Meta:
        verbose_name = _('receipt batch')
        verbose_name = _('receipt batches')

class Receipt(models.Model):
    """
    A receipt, as sent to AFIP.

    Note that AFIP allows sending ranges of receipts, but this isn't generally
    what you want, so we model invoices individually.

    You'll probably want to relate some `Sale` or `Order` object from your
    model with each Receipt.
    """
    batch = models.ForeignKey(
        ReceiptBatch,
        related_name='receipts',
        null=True,
        blank=True,
        verbose_name=_('receipt batch'),
        help_text=_(
            'Receipts are validated in batches, so it must be assigned one '
            'before validation is possible.'),
    )
    concept = models.ForeignKey(
        ConceptType,
        verbose_name=_('concept'),
        related_name='receipts',
    )
    document_type = models.ForeignKey(
        DocumentType,
        verbose_name=_('document type'),
        related_name='receipts',
        help_text=_(
            'The document type of the customer to whom this receipt '
            'is addressed'
        ),
    )
    document_number = models.BigIntegerField(
        _('document number'),
        help_text=_(
            'The document number of the customer to whom this receipt '
            'is addressed'
        )
    )
    # NOTE: WS will expect receipt_from and receipt_to.
    receipt_number = models.PositiveIntegerField(
        _('receipt number'),
        null=True,
    )
    issued_date = models.DateField(
        verbose_name=_('issued date'),
        help_text=_('Can diverge up to 5 days for good, or 10 days otherwise'),
    )
    total_amount = models.DecimalField(
        # ImpTotal
        _('total amount'),
        max_digits=15,
        decimal_places=2,
        help_text=_(
            'Must be equal to untaxed amount + exempt amount + taxes + vat.'
        )
    )
    net_untaxed = models.DecimalField(
        # ImpTotConc
        _('total untaxable amount'),
        max_digits=15,
        decimal_places=2,
        help_text=_(
            'The total amount to which taxes do not apply. '
            'For C-type receipts, this must be zero.'
        ),
    )
    net_taxed = models.DecimalField(
        # ImpNeto
        _('total taxable amount'),
        max_digits=15,
        decimal_places=2,
        help_text=_(
            'The total amount to which taxes apply. '
            'For C-type receipts, this is equal to the subtotal.'
            ),
    )
    exempt_amount = models.DecimalField(
        # ImpOpEx
        # Sólo para emisores que son IVA exento
        _('exempt amount'),
        max_digits=15,
        decimal_places=2,
        help_text=_(
            'Only for categories which are tax-exempt. '
            'For C-type receipts, this must be zero.'
        ),
    )
    vat_amount = models.DecimalField(
        _('vat amount'),
        max_digits=15,
        decimal_places=2,
        help_text=_('Must be equal to the sum of all Vat objects.'),
    )
    tax_amount = models.DecimalField(
        _('tax amount'),
        max_digits=15,
        decimal_places=2,
        help_text=_('Must be equal to the sum of all Tax objects.'),
    )
    service_start = models.DateField(
        _('service start date'),
        help_text=_(
            'Date on which a service started. No applicable for goods.'
        ),
        null=True,
        blank=True,
    )
    service_end = models.DateField(
        _('service end date'),
        help_text=_(
            'Date on which a service ended. No applicable for goods.'
        ),
        null=True,
        blank=True,
    )
    expiration_date = models.DateField(
        _('receipt expiration date'),
        help_text=_(
            'Date on which this receipt expires. No applicable for goods.'
        ),
        null=True,
        blank=True,
    )
    currency = models.ForeignKey(
        CurrencyType,
        verbose_name=_('currency'),
        related_name='documents',
        help_text=_(
            'Currency in which this receipt is issued.',
        ),
    )
    currency_quote = models.DecimalField(
        _('currency quote'),
        max_digits=10,
        decimal_places=6,
        help_text=_(
            'Quote of the day for the currency used in the receipt',
        ),
    )
    related_receipts = models.ManyToManyField(
        'Receipt',
        _('related receipts'),
        blank=True,
    )

    # Not implemented: optionals

    # TODO: methods to compute total, vat_amount and taxes_amount

    # These two values are stored in the receipt's batch. However, before the
    # receipt is assigned into a batch, this value should be used.
    receipt_type = models.ForeignKey(ReceiptType)
    point_of_sales = models.ForeignKey(PointOfSales)

    def __str__(self):
        return '{} #{}'.format(self.receipt_type, self.receipt_number)

    class Meta:
        verbose_name = _('receipt')
        verbose_name_plural = _('receipts')
        unique_together = (
            ('receipt_type', 'receipt_number',)
        )
        # TODO: index_together...

class Tax(models.Model):
    tax_type = models.ForeignKey(
        TaxType,
        verbose_name=_('tax type'),
    )
    description = models.CharField(
        _('description'),
        max_length=80,
    )
    base_amount = models.DecimalField(
        _('base amount'),
        max_digits=15,
        decimal_places=2,
    )
    aliquot = models.DecimalField(
        _('aliquot'),
        max_digits=5,
        decimal_places=2,
    )
    amount = models.DecimalField(
        _('amount'),
        max_digits=15,
        decimal_places=2,
    )

    receipt = models.ForeignKey(Receipt)

    class Meta:
        verbose_name = _('tax')
        verbose_name_plural = _('taxes')

class Vat(models.Model):
    vat_type = models.ForeignKey(
        VatType,
        verbose_name=_('vat type'),
    )
    base = models.DecimalField(
        _('base amount'),
        max_digits=15,
        decimal_places=2,
    )
    amount = models.DecimalField(
        _('amount'),
        max_digits=15,
        decimal_places=2,
    )

    receipt = models.ForeignKey(Receipt)

    class Meta:
        verbose_name = _('vat')
        verbose_name_plural = _('vat')

class Validation(models.Model):
    RESULT_APPROVED = 'A'
    RESULT_REJECTED = 'R'
    RESULT_PARTIAL = 'P'

    processed_date = models.DateField(
        _('processed date'),
    )
    result = models.CharField(
        _('result'),
        max_length=1,
        choices=(
            (RESULT_APPROVED, _('approved')),
            (RESULT_REJECTED, _('rejected')),
            (RESULT_PARTIAL, _('partial')),
        ),
    )

    batch = models.ForeignKey(
        ReceiptBatch,
        related_name='validation',
        verbose_name=_('receipt batch'),
    )

    class Meta:
        verbose_name = _('validation')
        verbose_name_plural = _('validations')

class Observation(models.Model):
    code = models.PositiveSmallIntegerField(
        _('code'),
    )
    message = models.CharField(
        _('message'),
        max_length=255,
    )

    class Meta:
        verbose_name = _('observation')
        verbose_name_plural = _('observations')

class ReceiptValidation(models.Model):
    validation = models.ForeignKey(
        Validation,
        verbose_name=_('validation'),
        related_name='receipts',
    )
    result = models.CharField(
        _('result'),
        max_length=1,
        choices=(
            (Validation.RESULT_APPROVED, _('approved')),
            (Validation.RESULT_REJECTED, _('rejected')),
            (Validation.RESULT_PARTIAL, _('partial')),
        ),
    )
    cae = models.CharField(
        _('cae'),
        max_length=14
    )
    cae_expiration = models.DateTimeField(
        _('cae expiration'),
    )
    observations = models.ForeignKey(
        Observation,
        verbose_name=_('observations'),
    )

    receipt = models.OneToOneField(
        Receipt,
        related_name='validation',
        verbose_name=_('receipt'),
    )

    class Meta:
        verbose_name = _('receipt validation')
        verbose_name_plural = _('receipt validations')
<|endoftext|>


<commit_before># Copyright (c) 2016 Fabian Kochem

import json

from libtree.core.node_data import NodeData
from libtree.core.query import get_ancestors, get_node
from libtree.utils import recursive_dict_merge

def get_nodes_by_property_dict(cur, query):
    """
    Return an iterator that yields a ``NodeData`` object of every node
    which contains all key/value pairs of ``query`` in its property
    dictionary. Inherited keys are not considered.

    :param dict query: The dictionary to search for
    """
    sql = """
        SELECT
          *
        FROM
          nodes
        WHERE
          properties @> %s;
    """
    cur.execute(sql, (json.dumps(query), ))
    for result in cur:
        yield NodeData(**result)

def get_nodes_by_property_key(cur, key):
    """
    Return an iterator that yields a ``NodeData`` object of every node
    which contains ``key`` in its property dictionary. Inherited keys
    are not considered.

    :param str key: The key to search for
    """
    sql = """
        SELECT
          *
        FROM
          nodes
        WHERE
          properties ? %s;
    """
    cur.execute(sql, (key, ))
    for result in cur:
        yield NodeData(**result)

def get_nodes_by_property_value(cur, key, value):
    """
    Return an iterator that yields a ``NodeData`` object of every node
    which has ``key`` exactly set to ``value`` in its property
    dictionary. Inherited keys are not considered.

    :param str key: The key to search for
    :param object value: The exact value to sarch for
    """
    query = {key: value}
    for node in get_nodes_by_property_dict(cur, query):
        yield node

def get_inherited_properties(cur, node):
    """
    Get the entire inherited property dictionary.

    To calculate this, the trees path from root node till ``node`` will
    be traversed. For each level, the property dictionary will be merged
    into the previous one. This is a simple merge, only the first level
    of keys will be combined.

    :param node:
    :type node: Node or uuid4
    :rtype: dict
    """
    ret = {}
    id = str(node)
    if isinstance(node, str):
        node = get_node(cur, id)

    ancestors = list(get_ancestors(cur, id))

    for ancestor in ancestors:
        ret.update(ancestor.properties)

    ret.update(node.properties)

    return ret

def get_inherited_property_value(cur, node, key):
    """
    Get the inherited value for a single property key.

    :param node:
    :type node: Node or uuid4
    :param key: str
    """
    return get_inherited_properties(cur, node)[key]

def get_recursive_properties(cur, node):
    """
    Get the entire inherited and recursively merged property dictionary.

    To calculate this, the trees path from root node till ``node`` will
    be traversed. For each level, the property dictionary will be merged
    into the previous one. This is a recursive merge, so all dictionary
    levels will be combined.

    :param node:
    :type node: Node or uuid4
    :rtype: dict
    """
    ret = {}
    id = str(node)
    if isinstance(node, str):
        node = get_node(cur, id)

    ancestors = list(get_ancestors(cur, id))

    for ancestor in ancestors:
        recursive_dict_merge(ret, ancestor.properties, create_copy=False)
        print(ret)
        # ret.update(ancestor.properties)

    recursive_dict_merge(ret, node.properties, create_copy=False)

    return ret

def set_properties(cur, node, new_properties):
    """
    Set the property dictionary to ``new_properties``.
    Return ``NodeData`` object with updated properties.

    :param node:
    :type node: Node or uuid4
    :param new_properties: dict
    """
    if not isinstance(new_properties, dict):
        raise TypeError('Only dictionaries are supported.')

    id = str(node)
    if isinstance(node, str):
        node = get_node(cur, id)

    sql = """
        UPDATE
          nodes
        SET
          properties=%s
        WHERE
          id=%s;
    """
    cur.execute(sql, (json.dumps(new_properties), str(node)))

    kwargs = node.to_dict()
    kwargs['properties'] = new_properties
    return NodeData(**kwargs)

def update_properties(cur, node, new_properties):
    """
    Update existing property dictionary with another dictionary.
    Return ``NodeData`` object with updated properties.

    :param node:
    :type node: Node or uuid4
    :param new_properties: dict
    """
    if not isinstance(new_properties, dict):
        raise TypeError('Only dictionaries are supported.')

    id = str(node)
    if isinstance(node, str):
        node = get_node(cur, id)

    properties = node.properties.copy()
    properties.update(new_properties)
    return set_properties(cur, node, properties)

def set_property_value(cur, node, key, value):
    """
    Set the value for a single property key.
    Return ``NodeData`` object with updated properties.

    :param node:
    :type node: Node or uuid4
    :param key: str
    :param value: object
    """
    id = str(node)
    if isinstance(node, str):
        node = get_node(cur, id)

    properties = node.properties.copy()
    properties[key] = value
    set_properties(cur, node, properties)

    kwargs = node.to_dict()
    kwargs['properties'] = properties
    return NodeData(**kwargs)
<commit_msg>Remove debug message<commit_after># Copyright (c) 2016 Fabian Kochem

import json

from libtree.core.node_data import NodeData
from libtree.core.query import get_ancestors, get_node
from libtree.utils import recursive_dict_merge

def get_nodes_by_property_dict(cur, query):
    """
    Return an iterator that yields a ``NodeData`` object of every node
    which contains all key/value pairs of ``query`` in its property
    dictionary. Inherited keys are not considered.

    :param dict query: The dictionary to search for
    """
    sql = """
        SELECT
          *
        FROM
          nodes
        WHERE
          properties @> %s;
    """
    cur.execute(sql, (json.dumps(query), ))
    for result in cur:
        yield NodeData(**result)

def get_nodes_by_property_key(cur, key):
    """
    Return an iterator that yields a ``NodeData`` object of every node
    which contains ``key`` in its property dictionary. Inherited keys
    are not considered.

    :param str key: The key to search for
    """
    sql = """
        SELECT
          *
        FROM
          nodes
        WHERE
          properties ? %s;
    """
    cur.execute(sql, (key, ))
    for result in cur:
        yield NodeData(**result)

def get_nodes_by_property_value(cur, key, value):
    """
    Return an iterator that yields a ``NodeData`` object of every node
    which has ``key`` exactly set to ``value`` in its property
    dictionary. Inherited keys are not considered.

    :param str key: The key to search for
    :param object value: The exact value to sarch for
    """
    query = {key: value}
    for node in get_nodes_by_property_dict(cur, query):
        yield node

def get_inherited_properties(cur, node):
    """
    Get the entire inherited property dictionary.

    To calculate this, the trees path from root node till ``node`` will
    be traversed. For each level, the property dictionary will be merged
    into the previous one. This is a simple merge, only the first level
    of keys will be combined.

    :param node:
    :type node: Node or uuid4
    :rtype: dict
    """
    ret = {}
    id = str(node)
    if isinstance(node, str):
        node = get_node(cur, id)

    ancestors = list(get_ancestors(cur, id))

    for ancestor in ancestors:
        ret.update(ancestor.properties)

    ret.update(node.properties)

    return ret

def get_inherited_property_value(cur, node, key):
    """
    Get the inherited value for a single property key.

    :param node:
    :type node: Node or uuid4
    :param key: str
    """
    return get_inherited_properties(cur, node)[key]

def get_recursive_properties(cur, node):
    """
    Get the entire inherited and recursively merged property dictionary.

    To calculate this, the trees path from root node till ``node`` will
    be traversed. For each level, the property dictionary will be merged
    into the previous one. This is a recursive merge, so all dictionary
    levels will be combined.

    :param node:
    :type node: Node or uuid4
    :rtype: dict
    """
    ret = {}
    id = str(node)
    if isinstance(node, str):
        node = get_node(cur, id)

    ancestors = list(get_ancestors(cur, id))

    for ancestor in ancestors:
        recursive_dict_merge(ret, ancestor.properties, create_copy=False)

    recursive_dict_merge(ret, node.properties, create_copy=False)

    return ret

def set_properties(cur, node, new_properties):
    """
    Set the property dictionary to ``new_properties``.
    Return ``NodeData`` object with updated properties.

    :param node:
    :type node: Node or uuid4
    :param new_properties: dict
    """
    if not isinstance(new_properties, dict):
        raise TypeError('Only dictionaries are supported.')

    id = str(node)
    if isinstance(node, str):
        node = get_node(cur, id)

    sql = """
        UPDATE
          nodes
        SET
          properties=%s
        WHERE
          id=%s;
    """
    cur.execute(sql, (json.dumps(new_properties), str(node)))

    kwargs = node.to_dict()
    kwargs['properties'] = new_properties
    return NodeData(**kwargs)

def update_properties(cur, node, new_properties):
    """
    Update existing property dictionary with another dictionary.
    Return ``NodeData`` object with updated properties.

    :param node:
    :type node: Node or uuid4
    :param new_properties: dict
    """
    if not isinstance(new_properties, dict):
        raise TypeError('Only dictionaries are supported.')

    id = str(node)
    if isinstance(node, str):
        node = get_node(cur, id)

    properties = node.properties.copy()
    properties.update(new_properties)
    return set_properties(cur, node, properties)

def set_property_value(cur, node, key, value):
    """
    Set the value for a single property key.
    Return ``NodeData`` object with updated properties.

    :param node:
    :type node: Node or uuid4
    :param key: str
    :param value: object
    """
    id = str(node)
    if isinstance(node, str):
        node = get_node(cur, id)

    properties = node.properties.copy()
    properties[key] = value
    set_properties(cur, node, properties)

    kwargs = node.to_dict()
    kwargs['properties'] = properties
    return NodeData(**kwargs)
<|endoftext|>


<commit_before>open("filepath")  # $getAPathArgument="filepath"
open(file="filepath")  # $getAPathArgument="filepath"

o = open

o("filepath")  # $ MISSING: getAPathArgument="filepath"
o(file="filepath")  # $ MISSING: getAPathArgument="filepath"
<commit_msg>Python: Expand tests of `open`<commit_after>import builtins
import io

open("filepath")  # $getAPathArgument="filepath"
open(file="filepath")  # $getAPathArgument="filepath"

o = open

o("filepath")  # $ MISSING: getAPathArgument="filepath"
o(file="filepath")  # $ MISSING: getAPathArgument="filepath"

builtins.open("filepath")  # $ MISSING: getAPathArgument="filepath"
builtins.open(file="filepath")  # $ MISSING: getAPathArgument="filepath"

io.open("filepath")  # $ MISSING: getAPathArgument="filepath"
io.open(file="filepath")  # $ MISSING: getAPathArgument="filepath"
<|endoftext|>