# Dynamic Settings

Dynamic settings allow for the flexible and customizable management of various aspects of your system. This document provides an overview of the key configurations available through dynamic settings, each designed to address specific operational needs and enhance the functionality of your platform.

### **1. Subscription Gateways**

Defines the set of subscription/marketing gateways used for user membership and consent (e.g. newsletter, SMS, push). Each entry is keyed by gateway name and contains the gateway class path, optional dispatch service, and a `conf` object with credentials and options. The system resolves the active gateway(s) via `ACTIVE_SUBSCRIPTION_GATEWAYS` or `SUBSCRIPTION_GATEWAY`, then looks up settings here by name. Used when syncing users to external platforms (EuroMessage, Insider, Mobildev, IYS, Emarsys, MailChimp, etc.) and when sending consent or preference data.

**Usage:** Read by `omnishop.users.service` when resolving gateway settings with `get_gateway_settings(gateway_name)`; each gateway is instantiated with `klass` and `conf` so integrations can authenticate and communicate with the provider.

Key: `SUBSCRIPTION_GATEWAYS`\
Type: object (dict of gateway name → gateway config)

**Example:**

```
{
    "emarsys": {
        "klass": "omnishop.libs.subscription_gateways.emarsys.gateway.EmarsysGateWay",
        "dispatch_klass": "omnishop.libs.subscription_gateways.service.GenericDispatchService",
        "conf": {
            "api_key": "...",
            "api_secret": "..."
        }
    },
    "insider": {
        "klass": "omnishop.libs.subscription_gateways.insider.gateway.InsiderGateway",
        "dispatch_klass": "omnishop.libs.subscription_gateways.service.GenericDispatchService",
        "conf": { ... }
    }
}
```

### **2. Default Country Code**

The ISO country code (e.g. `tr`, `pl`, `en`) used as the default market for the brand. It must match an existing `Country` record (by `code`). This value is used to pre-select the country on address forms, resolve country-dependent resources (e.g. LCW SSO), and provide a default country in loyalty/subscription flows (e.g. Emarsys). It affects checkout address pages, user resources, and SSO gateways that need a single default country.

**Usage:** Used in `omnishop.orders.pages.address` and `omnishop.users.resources.views` via `CountryService().get_country(dynamic_settings.DEFAULT_COUNTRY_CODE)`; in `omnishop.address.jinja_globals` for templates; and in subscription gateways (e.g. Emarsys) when a default country is required.

Key: `DEFAULT_COUNTRY_CODE`\
Type: string

**Example:** `"tr"` (Turkey), `"pl"` (Poland), `"en"` (if used as a country code in your data).

### **3. Identity Number Validator**

Dotted path to the callable that validates the address identity number (e.g. national ID). The validator is invoked when address data is validated; it receives the identity number value and must raise if invalid. Use this to enforce country-specific rules (e.g. Turkish ID format) or to disable validation.

**Usage:** Loaded in `omnishop.address.resources.serializers` with `import_string(dynamic_settings.IDENTITY_NUMBER_VALIDATOR)` and called during address validation.

Key: `IDENTITY_NUMBER_VALIDATOR`\
Type: string (dotted path)

**Example:**

* `omnicore.address.validators.null_identity_validator` — no validation.
* `omnicore.address.validators.tc_identity_number_validator` — Turkish Republic identity number format.

### **4. Default Shipping Cost**

Fallback shipping cost (numeric or string that parses to decimal) used when no matching `ShippingCost` rule is found for the given address (country, city, township, district) and currency. If a rule exists for a more specific region it overrides this; otherwise the checkout and shipping calculator use this value so the order still has a defined shipping amount.

**Usage:** In `omnishop.address.service.ShippingCostService.get_cost()`: if there is no shipping address, or no `ShippingCost` row matches the address hierarchy, the method returns `Decimal(dynamic_settings.DEFAULT_SHIPPING_COST)`.

Key: `DEFAULT_SHIPPING_COST`\
Type: number (int/float) or string

**Example:** `0`, `"0.00"`, or `9.99`.

### **5. Sitemap Configuration**

Per-sitemap configuration for SEO XML sitemaps. Each key is a sitemap type: `landing_page`, `category`, `flat_page`, `special_page`, `product`. Each value controls whether that sitemap is enabled, which template is used, changefreq, priority, limit, i18n, excludes/filters, and (for product) whether out-of-stock products are included. Sitemap classes read this via `dynamic_settings.SITEMAP_CONFIGURATION[setting_slug]` and use it to decide if they return URLs and how they are built.

**Usage:** Consumed by `omnishop.libs.sitemap` and `omnishop.cms.settings.conf`; each `BaseSitemap` subclass gets its config from `SITEMAP_CONFIGURATION` and uses `enabled`, `template`, `changefreq`, `priority`, `limit`, `excludes`, `filters`, `i18n`; product sitemap also uses `include_stock_out_products`.

Key: `SITEMAP_CONFIGURATION`\
Type: object

**Example (product section):**

```
"product": {
    "enabled": true,
    "changefreq": "daily",
    "priority": 0.5,
    "limit": 50000,
    "template": "sitemap.xml",
    "should_generate": true,
    "excludes": {},
    "filters": {},
    "i18n": false,
    "include_stock_out_products": true
}
```

### **6. Customer Loyalty Card Service**

Selects the loyalty-card backend and its options. The `klass` is the dotted path of the service class (e.g. default, Akipay, Zubizu, Tefal, Obase, Como, Giz, Loccitane, Nebim, Rapidpromo); `conf` is a free-form object used for API URLs, auth, minimum basket amount, JWT path, etc. This service is used for loyalty views (create/update cards, query points), promotion flows that need card data, and user views that show or link loyalty information.

**Usage:** Read in `omnishop.loyaltycard.views`, `omnishop.loyaltycard.client`, `omnishop.promotions.service`, and `omnishop.users.views`; the client uses `conf` for auth (e.g. `auth.auth_url`, `jwt_token_path`) and optional `minimum_basket_amount` (decimal, ≥ 0).

Key: `CUSTOMER_LOYALTY_CARD_SERVICE`\
Type: object

**Example:**

```
{
    "klass": "omnishop.loyaltycard.service.DefaultLoyaltyCardService",
    "conf": {
        "auth": {
            "auth_url": "https://api.example.com/auth",
            "token_expiration": 3600
        },
        "jwt_token_path": ["Data", "Token"],
        "minimum_basket_amount": "100.00"
    }
}
```

### **7. Cash Register Authentication Configuration**

Credentials used to authenticate requests that originate from a Cash Register (e.g. creating or accessing a pre-order by order number). The incoming request’s username and password are compared to this setting; only when both match is the operation allowed. This protects cash-register-only endpoints from unauthorized use.

**Usage:** In `omnishop.orders.resources.views.check_user_name()`: `conf = dynamic_settings.CASH_REGISTER_AUTH_CONF` and validation succeeds when `data.get("username") == conf['username']` and `data.get("password") == conf['password']`. Referenced from supervising/API views that need to expose or validate this key.

Key: `CASH_REGISTER_AUTH_CONF`\
Type: object

**Example:**

```
{
    "username": "cr_user",
    "password": "secure_cr_password"
}
```

### **8. Three D Secure Enabled**

Master switch for 3D Secure (3DS) on payment flows. When `true`, the payment pipeline may require a 3DS step (e.g. redirect to issuer) depending on POS/card configuration and `THREE_D_SECURE_RULES`. When `false`, 3DS is not applied regardless of rules. Used together with POS/card `three_d_enabled` and the rule functions that decide per request whether to trigger 3DS (e.g. by amount, guest user, currency).

**Usage:** Checked in `omnishop.orders.rules.check_three_d_secure()`: if `not dynamic_settings.THREE_D_SECURE_ENABLED` the function returns `False` and 3DS is skipped. Also used in `is_first_order` and related tests.

Key: `THREE_D_SECURE_ENABLED`\
Type: boolean

**Example:** `true` to enable 3DS when rules and POS/card allow; `false` to disable globally.

### **9. Three D Secure Rules**

List of rule entries that decide whether to apply 3D Secure for a given payment. Each entry has `klass` (dotted path to a rule function) and `defaults` (kwargs passed to that function). 3DS is applied only when `THREE_D_SECURE_ENABLED` is true, POS/card allow it, and at least one rule returns true (or the list is empty). Rule functions receive payment context (pos, user, amount, currency, etc.) and return a boolean.

**Where it is used**

* **omnishop.orders.rules.check\_three\_d\_secure()** — `rules = dynamic_settings.THREE_D_SECURE_RULES`; each rule's `klass` is imported and called with `defaults` merged with kwargs; results are ORed. Used to enforce 3DS by amount, guest user, first order, currency, POS list, or trial limits.

Key: `THREE_D_SECURE_RULES`\
Type: array of objects

**Example**

```python
THREE_D_SECURE_RULES = [
    {
        'klass': 'omnishop.orders.rules.amount_limit',
        'defaults': {
            'amount_limit': '1000',
        },
    },
    {
        'klass': 'omnishop.orders.rules.trial_limit_per_user_email',
        'defaults': {
            'user_trial_limit': '2',
            'timeout': 60 * 60,
        },
    },
    {
        'klass': 'omnishop.orders.rules.trial_limit_per_remote_addr',
        'defaults': {
            'remote_addr_trial_limit': '2',
            'timeout': 60 * 60,
        },
    },
]
```

#### **1. Amount Limit Rule**

3D Secure is required when the payment amount is above a given limit.

```json
{
    "klass": "omnishop.orders.rules.amount_limit",
    "defaults": {
        "amount_limit": "1000"
    }
}
```

#### **2. Masterpass Amount Limit Rule**

3D Secure is required when the payment amount with Masterpass is above a given limit.

```json
{
    "klass": "omnishop.orders.rules.masterpass_amount_limit",
    "defaults": {
        "default_amount_limit": "1000",
        "masterpass_amount_limit": 200
    }
}
```

#### **3. Three-D Pos List Rule**

3D Secure is required for payments when the POS slug is in the configured list.

```json
{
    "klass": "omnishop.orders.rules.check_pos_slug_three_d_only",
    "defaults": {
        "three_d_pos_slug_list": ["pos1", "pos2", "pos3"]
    }
}
```

#### **4. Trial Limit per User Rule**

A trial limit per user email is applied within a time window. If the user exceeds the number of trials in that period, 3D Secure is required.

```json
{
    "klass": "omnishop.orders.rules.trial_limit_per_user_email",
    "defaults": {
        "user_trial_limit": "2",
        "timeout": 3600
    }
}
```

`timeout` is in seconds (e.g. 3600 = 1 hour).

#### **5. Trial Limit per IP Address Rule**

A trial limit per remote (IP) address is applied within a time window. If the limit is exceeded, 3D Secure is required.

```json
{
    "klass": "omnishop.orders.rules.trial_limit_per_remote_addr",
    "defaults": {
        "remote_addr_trial_limit": "2",
        "timeout": 3600
    }
}
```

`timeout` is in seconds (e.g. 3600 = 1 hour).

#### **6. Guest User Rule**

Guest (unauthenticated) users are always required to perform 3D Secure.

```json
{
    "klass": "omnishop.orders.rules.only_guest_user",
    "defaults": {}
}
```

#### **7. First Order Rule**

3D Secure is required when the order is the user's first order. For guests, the behaviour depends on `THREE_D_SECURE_ENABLED`. For registered users, if the user has no orders, 3D Secure is required.

```json
{
    "klass": "omnishop.orders.rules.is_first_order",
    "defaults": {}
}
```

#### **8. Payment currency Rule**

3D Secure is required for payments in specific currencies.

```json
{
    "klass": "omnishop.orders.rules.payment_currency",
    "defaults": {
        "currencies": ["try", "eur"]
    }
}
```

**Supported currency codes**

```json
[
    "try", "eur", "usd", "egp", "gbp", "mad", "pln", "sar", "ron", "uah",
    "czk", "huf", "rub", "bgn", "iqd", "kwd", "bhd", "omr", "qar", "aed",
    "ngn", "inr", "lei", "kzt", "jod", "rsd", "amd", "cfa", "lyd"
]
```

### **10. Identity Number Required Amount**

Order total threshold (numeric) above which the shipping address must have an identity number. If set (e.g. 300), checkout validation compares the pre-order total (with interest) to this value; when the total is greater and the selected shipping address has no identity number, a validation error is raised so the user must provide it. Set to `null` or omit to not require identity number by amount.

**Usage:** In `omnishop.orders.serializers.input_serializers` (e.g. address/checkout input validation): `amount_limit = dynamic_settings.IDENTITY_NUMBER_REQUIRED_AMOUNT`; if `amount_limit is not None` and `pre_order.get_total_amount_with_interest() > amount_limit` and the shipping address has no `identity_number`, a `ValidationError` is raised for the identity number field.

Key: `IDENTITY_NUMBER_REQUIRED_AMOUNT`\
Type: number (int/float) or null

**Example:** `300` — require identity number for orders over 300 (in the order currency); `null` — do not enforce by amount.

### **11. Anonymous Address Expire Seconds**

Lifetime in seconds for anonymous users’ session-stored address references. When an anonymous user adds, updates, or deletes an address, the session stores the address hash(es) and an expiry timestamp set to *now + this value*. When reading addresses, the service returns them only if the stored expiry time is still in the future; after that, the addresses are no longer returned (effectively expired). Logged-in users are not affected.

**Usage:** In `omnishop.utils.sessions.AnonymousUserAddressService`: on add/update/delete of anonymous address, `request.session[address_expire_key] = time.time() + dynamic_settings.ANONYMOUS_ADDRESS_EXPIRE_SECONDS`; in `get_anonymous_addresses()`, addresses are returned only when `expire_time and (expire_time > time.time())`.

Key: `ANONYMOUS_ADDRESS_EXPIRE_SECONDS`\
Type: number (int)

**Example:** `1800` (30 minutes); default is `60 * 30` if not set in Django settings.

### **12. Offer Show Listing Kwargs**

When `true`, queries that fetch active basket offers (campaigns) for product listing or detail use `select_related` for `promotion`, `condition`, `benefit`, and their `product_collection` relations so that full offer details are loaded in one go. When `false`, those relations are not eagerly loaded, reducing query size and cost but requiring extra queries if listing code needs promotion/condition/benefit data. Use `true` when listing or product APIs need to expose promotion, condition, and benefit information per product.

**Usage:** Read in `omnishop.promotions.models.get_basket_offers_from_cache()`, `omnishop.products.managers` (active offers prefetch), and `omnishop.products.resources.serializers`; when true, the queryset is extended with `.select_related("promotion", "condition", "benefit", "condition__product_collection", "benefit__product_collection")`.

Key: `OFFER_SHOW_LISTING_KWARGS`\
Type: boolean

**Example:** `true` to include full offer details in listing/campaign queries; `false` (default) to avoid extra joins.

### **13. Test User Emails**

List of email addresses that are treated as “test users” for visibility of hidden products. If a product is marked hidden (`is_hidden`), the product detail view allows access only when the request user is authenticated *and* the user’s email is in this list; otherwise the view raises `Http404`. Use this to let internal or test accounts view products that are hidden from normal users.

**Usage:** In `omnishop.products.resources.views` (product detail): when `product.is_hidden`, access is allowed only if `request.user.is_authenticated` and `request.user.email in dj_dynamic_settings.TEST_USER_EMAILS`; otherwise `raise Http404`.

Key: `TEST_USER_EMAILS`\
Type: array (list of strings)

**Example:** `["qa@example.com", "test@test.com"]`. Default: `[]`.

### **14. Product End Of Life Attribute**

Product attribute key (e.g. `end_of_life`) whose value indicates that the product is no longer sold. For a single product: if this attribute is truthy *and* the product has no stock, the product detail view returns `Http404`. For a grouped product: if this attribute is truthy, the view returns `Http404` regardless of stock. Use it to hide discontinued or end-of-life products from the storefront.

**Usage:** In `omnishop.products.resources.views`: on product detail, `eol_attr = dj_dynamic_settings.PRODUCT_END_OF_LIFE_ATTRIBUTE`; if `product.attributes.get(eol_attr)` is truthy and (for single product) `not product.has_any_stock()`, or for grouped product if the attribute is set, the view raises `Http404`. Same attribute is used on grouped product detail view.

Key: `PRODUCT_END_OF_LIFE_ATTRIBUTE`\
Type: string

**Example:** `"end_of_life"` or `"discontinued"` — the product’s attributes dict must contain this key with a truthy value (and no stock for single products) to trigger 404.

### **15. Pass Stock Check on Detail**

When `true`, the product detail page service is called with `pass_stock_check=True`, so the detail can be returned even when the product would normally fail a stock or listability check (e.g. out of stock). When `false`, the usual stock check applies and the detail may not be shown or may redirect if the product is not listable. Use `true` to allow viewing detail for out-of-stock or otherwise non-listable products (e.g. for SEO or “notify me” flows).

**Usage:** In `omnishop.products.resources.views` (product detail): `pass_stock_check = dj_dynamic_settings.PASS_STOCK_CHECK_ON_DETAIL` is passed to `self.service.get_detail_page(..., pass_stock_check=pass_stock_check)`; the service uses it to skip or relax stock checks when building the detail response.

Key: `PASS_STOCK_CHECK_ON_DETAIL`\
Type: boolean

**Example:** `true` to show detail regardless of stock; `false` (default) to enforce stock/listability on detail.

### **16. Category Detail Max Depth**

Maximum category depth at which the category page is shown as a “landing” view (e.g. with subcategory tiles). If the current category’s depth is greater than this value (and `force_landing` is not set), the view switches to a listing/facet view that shows products in that category instead of a deeper landing. So lower values (e.g. 2) make more categories behave as listing pages; higher values allow deeper categories to still show as landing pages with subcategories.

**Usage:** In `omnishop.products.resources.views.CategoryDetailPageView.get()`: `max_depth = dj_dynamic_settings.CATEGORY_DETAIL_MAX_DEPTH`; if `category_node.depth > max(1, max_depth)` and not `force_landing`, a facet view is used (listing); otherwise the category landing is used.

Key: `CATEGORY_DETAIL_MAX_DEPTH`\
Type: number (int)

**Example:** `2` — categories at depth 3 or more show as listing; `3` — only depth 4+ show as listing. Default: `2`.

### **17. Product Stock Out Visibility Enabled**

When `true`, search and listing queries do not filter out out-of-stock products: the base Elasticsearch query uses an “exists” clause on the stock field instead of “stock > 0”, so products with zero stock can appear in results. When `false`, only products with stock > 0 are returned. Also controls what gets indexed: the search indexer includes or excludes out-of-stock products according to this setting. Changing it triggers a post-save action to refresh the index.

**Usage:** In `omnishop.search.utils.get_base_es_query()`: if true, `stock_q = Q("exists", field=stock_field)`; otherwise `stock_q = Q("range", stock_field: {"gt": 0})`. Used in `omnishop.search.services`, `omnishop.search.indexer` for indexing and query building. Has a post\_save\_action to update index (e.g. refresh sitemap/stock visibility).

Key: `PRODUCT_STOCK_OUT_VISIBILITY_ENABLED`\
Type: boolean

**Example:** `true` to show out-of-stock products in search and listing; `false` (default) to hide them.

### **18. Search Default Page Size**

Default number of results per page for search and catalog listing views when the client does not send a page size. Must be at least 1. Used as the default for pagination in search API and category/search result pages.

**Usage:** In `omnishop.search.resources.views`: `default_page_size = property(lambda self: dynamic_settings.SEARCH_DEFAULT_PAGE_SIZE)`; this value is used when building paginated search/catalog responses. Validated with `MinValueValidator(1)` in `omnishop.search.settings.conf`.

Key: `SEARCH_DEFAULT_PAGE_SIZE`\
Type: number (int), minimum 1

**Example:** `20` (default); `12` or `24` for smaller/larger pages.

### **19. Slot Days Count**

Number of days to generate delivery slots for, starting from the first allowed slot date (which is determined by `DEFERRED_SLOT_DAYS_COUNT`). For example, if this is 5 and the first slot day is Wednesday, slots are generated for Wednesday through Sunday. Passed to the slot service as the `days` argument when building the list of selectable delivery time ranges.

**Usage:** In `omnishop.slots.resources.views` and `omnishop.orders.pages.slots`: `slot_days_count = dynamic_settings.SLOT_DAYS_COUNT` is passed to `slot_service.generate_slots(days=slot_days_count, deferred_days=deferred_slot_days_count, ...)` to control how many days of slots are offered.

Key: `SLOT_DAYS_COUNT`\
Type: number (int)

**Example:** `2` — two days of slots; `5` — five days. Default: `2`.

### **20. Deferred Slot Days Count**

Number of days to skip from today before the first available delivery slot day. For example, if today is Monday and this is 2, the first slot day is Wednesday; slots are then generated for the next `SLOT_DAYS_COUNT` days starting from Wednesday. Passed to the slot service as `deferred_days` so that delivery options start from a future date (e.g. to account for preparation or cutoff rules).

**Usage:** In `omnishop.slots.resources.views` and `omnishop.orders.pages.slots`: `deferred_slot_days_count = dynamic_settings.DEFERRED_SLOT_DAYS_COUNT` is passed to `slot_service.generate_slots(..., deferred_days=deferred_slot_days_count, ...)`. Can be combined with `DEFERRED_SLOT_IGNORED_DAYS` for more control.

Key: `DEFERRED_SLOT_DAYS_COUNT`\
Type: number (int)

**Example:** `0` — first slot is today (default); `2` — first slot is in 2 days (e.g. Monday → Wednesday).

### **21. Auth Password Validators**

Defines the list of password validators applied when a user sets or changes their password. Each item is an object with **NAME** (dotted path to a class that extends Django `BasePasswordValidator`) and optional **OPTIONS** (kwargs passed to the validator). The same list is used in registration, password-change forms, and profile/serializer password validation so strength rules (length, capitals, numbers, common passwords, etc.) are applied consistently.

**Where it is used**

* **omnishop.users.forms** — `SetPasswordForm.clean_password2()` and `AllAuthSetPasswordForm.clean_password2()`: `auth_password_validators = dj_dynamic_settings.AUTH_PASSWORD_VALIDATORS`; then `validate_password(password, user=..., password_validators=get_password_validators(auth_password_validators))`.
* **omnishop.users.resources.serializers** — When validating password in user serializers (e.g. registration or profile update): validators are built from this setting and run against the submitted password.

**Allowed validator names (NAME)**\
Built-in choices in `PasswordValidatorSerializer` include: `MinimumLengthValidator`, `CommonPasswordValidator`, `NumericPasswordValidator`, `UserAttributeSimilarityValidator` (Django); `MaximumLengthValidator`, `MinimumCapitalLetterValidator`, `MinimumLowerCaseLetterValidator`, `MinimumLetterValidator`, `MinimumNumberValidator`, `MinimumSpecialCharacterValidator`, `PreviouslyUsedPasswordValidator` (omnicore); `OldPasswordValidator` (omnishop).

Key: `AUTH_PASSWORD_VALIDATORS`\
Type: array of objects (each: `NAME` string, `OPTIONS` object)\
Default: from Django settings `AUTH_PASSWORD_VALIDATORS`

**Example**

```
[
    {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", "OPTIONS": {"min_length": 8}},
    {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
    {"NAME": "omnicore.utils.password_validators.MinimumNumberValidator", "OPTIONS": {}}
]
```

### **23. Reset Email Html Template**

Template name (path) used for the **HTML part** of the password reset email sent when a user requests a password reset. If set, Django's `PasswordResetForm.send_mail()` is called with `html_email_template_name` set to this value, so the reset link email can use your custom HTML layout instead of the default.

**Where it is used**

* **omnishop.users.forms.PasswordResetForm** — In `send_mail()`: `kwargs['html_email_template_name'] = dj_dynamic_settings.RESET_EMAIL_HTML_TEMPLATE` before calling `super().send_mail(*args, **kwargs)`. Only has effect if the form sends HTML; the template receives the same context as the plain-text reset email (e.g. user, reset link).

Key: `RESET_EMAIL_HTML_TEMPLATE`\
Type: string (template path)\
Default: `getattr(settings, "RESET_EMAIL_HTML_TEMPLATE", None)` (often `None`)

**Example**

* `"emails/password_reset.html"` — use a project template under `templates/emails/password_reset.html`.
* `None` or unset — no HTML part or Django default behaviour.

### **24. Phone Number Unique Validator Active**

When `true`, the phone number field is validated so that it is unique across active users (no other active user can have the same phone). When `false`, the `UniqueValidator` for phone is not applied and duplicate phone numbers are allowed. Use this to enforce one-phone-per-account in registration and profile updates, or disable it to allow shared/duplicate phones (e.g. family or test data).

**Where it is used**

* **omnishop.users.resources.serializers** — Phone field validators use `ConditionalValidator(lambda: dj_dynamic_settings.PHONE_NUMBER_UNIQUE_VALIDATOR_ACTIVE, UniqueValidator(queryset=User.objects.filter(is_active=True), message=_('Phone number is already in use with another account.')))`. So when the setting is true, the uniqueness check runs; when false, it is skipped. Used on **RegisterSerializer** and any profile/serializer that includes the phone field (e.g. around lines 89, 249, 461).

Key: `PHONE_NUMBER_UNIQUE_VALIDATOR_ACTIVE`\
Type: boolean\
Default: `False`

**Example**

* `true` — registration and profile update will fail if the phone is already used by another active user.
* `false` — duplicate phone numbers are allowed.

### **25. Contact Us Email To**

Maps **operation** (or context) keys to the recipient email address(es) for the Contact Us form. Each value must be a single email string or a list of email strings; all are validated as valid emails. The form submits an **operation** (e.g. `defaults`, `contact_us`, `franchise`); the service looks up `CONTACT_US_EMAIL_TO[operation]` or falls back to `CONTACT_US_EMAIL_TO['defaults']` to build the **To** list for the email. Optional behaviour: `CONTACT_US_SEND_USER` adds the submitter to To; `CONTACT_US_REPLY_TO_ONLY_USER` sets Reply-To to the submitter only.

**Where it is used**

* **omnishop.users.resources.serializers** — In `validate_operation()`: `contact_us_emails = dj_dynamic_settings.CONTACT_US_EMAIL_TO`; the submitted operation must be one of `contact_us_emails.keys()`, otherwise a validation error is raised.
* **omnishop.users.service** — When sending the Contact Us email: `contact_us_emails = dj_dynamic_settings.CONTACT_US_EMAIL_TO`; `to = contact_us_emails.get(operation) or contact_us_emails.get('defaults')`; if `to` is a list/tuple it is used as-is, if a string it is wrapped in a single-element tuple. That `to` is used as the email To list (and optionally combined with the user email per `CONTACT_US_SEND_USER` / `CONTACT_US_REPLY_TO_ONLY_USER`).

Key: `CONTACT_US_EMAIL_TO`\
Type: object (dict: key → string or list of strings, all valid emails)\
Default: `{"defaults": "contactus@akinon.com"}` (or from Django settings)

**Example**

```
{
    "defaults": ["contactus@example.com"],
    "contact_us": "support@example.com",
    "franchise": ["franchise@example.com", "backup@example.com"]
}
```

The **operation** chosen in the form (e.g. `franchise`) selects the value for that key; if the key is missing, `defaults` is used.

### **26. Available Currencies**

List of currency codes that the shop supports (e.g. `["try", "usd", "eur"]`). Validation rules: the list must contain no duplicates and must include the value of `DEFAULT_CURRENCY`. Used to validate user-selected currency (e.g. in serializers) and to expose which currencies the frontend can offer in the API.

**Where it is used**

* **omnishop.users.settings.conf** — `AvailableCurrencies` validators ensure `len(currencies) == len(set(currencies))` and `dynamic_settings.DEFAULT_CURRENCY in currencies`.
* **omnishop.users.resources.serializers** — Currency choice validation checks that the value is in `dj_dynamic_settings.AVAILABLE_CURRENCIES` (e.g. `lambda currency_code: (currency_code in dj_dynamic_settings.AVAILABLE_CURRENCIES)`).
* **omnishop.utils.currency** — Asserts that the currency is in `dynamic_settings.AVAILABLE_CURRENCIES`.
* **omnishop.users.resources.views** — Response includes `"available_currencies": dynamic_settings.AVAILABLE_CURRENCIES` for the client.

Key: `AVAILABLE_CURRENCIES`\
Type: array (list of strings; choices from `CurrencyType.choices()`)\
Default: `[settings.DEFAULT_CURRENCY_TYPE]`

**Example**

```json
["try", "usd", "eur"]
```

Must include the value of `DEFAULT_CURRENCY` (e.g. if default is `try`, `try` must be in the list).

### **27. User Phone Regex**

Regular expression used to validate user phone numbers (format and length). Applied in validators, services, and allauth adapter; also exposed in the API so the frontend can show the expected format or run client-side validation. Some code strips the leading `^` for use in DB or display patterns.

**Where it is used**

* **omnishop.users.validators** — `regex = dynamic_settings.USER_PHONE_REGEX`; used to validate phone input.
* **omnishop.users.service** — `regex = dynamic_settings.USER_PHONE_REGEX.replace("^", "")` for formatting/validation logic.
* **omnishop.users.allauth\_adapter** — Same regex (with `^` stripped) for phone handling.
* **omnishop.users.resources.views** — Response includes `"user_phone_regex": dynamic_settings.USER_PHONE_REGEX` for the client.

Key: `USER_PHONE_REGEX`\
Type: string\
Default: `r'^(05)\d{9}$'` (Turkish mobile: 05 + 9 digits)

**Example**

* `^(05)\d{9}$` — Turkish mobile (05xxxxxxxxx).
* `^(09)\d{9}$` — Alternative 09-prefix format.
* `^\+90\d{10}$` — E.164-style Turkish.

### **28. User Phone Format**

Example phone string displayed in validation error messages so the user sees the expected format (e.g. placeholder or hint). Human-readable only; actual validation is done with `USER_PHONE_REGEX`. Use a value that matches your regex (e.g. same length and pattern).

**Where it is used**

* Used where phone validation errors are raised so the message can include this format (e.g. "Phone must match format: {USER\_PHONE\_FORMAT}").
* Default ensures a sensible Turkish-style example when the setting is not overridden.

Key: `USER_PHONE_FORMAT`\
Type: string\
Default: `"05999999999"`

**Example**

* `05999999999` — Turkish mobile placeholder.
* `05123456789` — Alternative example matching the same regex.

### **29. Active Subscription Gateways**

List of subscription gateway **names** (strings) that are currently active. Resolution order in `get_subscription_gateways()`: (1) if CMS gateways of type subscription exist, those are used; (2) else if this list is non-empty, this list is returned; (3) else `[SUBSCRIPTION_GATEWAY]` is returned. Each name in this list must exist as a key in `SUBSCRIPTION_GATEWAYS` so that `get_gateway_settings(gateway_name)` can load `klass` and `conf`. Use multiple names to run several gateways (e.g. Emarsys + Insider); use an empty list to rely on the single `SUBSCRIPTION_GATEWAY`.

**Where it is used**

* **omnishop.users.service** — In `get_subscription_gateways()`: `active_subscription_gateways = dj_dynamic_settings.ACTIVE_SUBSCRIPTION_GATEWAYS`; if `active_subscription_gateways` is truthy, the method returns it; otherwise returns `[subscription_gateway]`. The returned names are then used to instantiate gateways via `get_gateway_settings(name)` and `get_subscription_gateway(name)`.

Key: `ACTIVE_SUBSCRIPTION_GATEWAYS`\
Type: array (list of strings)\
Default: `getattr(settings, "ACTIVE_SUBSCRIPTION_GATEWAYS", [])`

**Example**

```json
["emarsys", "insider"]
```

* Two gateways active; both must have entries in `SUBSCRIPTION_GATEWAYS`.
* `[]` — no active list; `SUBSCRIPTION_GATEWAY` is used as the single gateway.

### **30. Subscription Gateway**

Single subscription gateway **name** used when `ACTIVE_SUBSCRIPTION_GATEWAYS` is empty. Must match a key in `SUBSCRIPTION_GATEWAYS`. The subscription service calls `get_gateway_settings(this_name)` to get the config dict (from DB or from `SUBSCRIPTION_GATEWAYS[name]`), then instantiates the gateway with `klass` and `conf`. Typical values are gateway slugs such as `emarsys`, `insider`, `revotas`, `setrow`, `euromessage`.

**Where it is used**

* **omnishop.users.service** — In `get_subscription_gateways()`: `subscription_gateway = dj_dynamic_settings.SUBSCRIPTION_GATEWAY`; when `ACTIVE_SUBSCRIPTION_GATEWAYS` is empty, the method returns `[subscription_gateway]`. That name is later passed to `get_gateway_settings(gateway_name)` which does `dj_dynamic_settings.SUBSCRIPTION_GATEWAYS.get(gateway_name, None)` if not found in CMS Gateway model.

Key: `SUBSCRIPTION_GATEWAY`\
Type: string\
Default: `getattr(settings, "SUBSCRIPTION_GATEWAY", None)`

**Example**

* `emarsys` — use the `emarsys` entry in `SUBSCRIPTION_GATEWAYS`.
* `revotas` — use Revotas gateway config.
* `insider` — use Insider gateway config.

The value must exist as a key in `SUBSCRIPTION_GATEWAYS` (or be provided by CMS Gateway settings).

### **31. Subscription Active**

Master switch for subscription/membership features (newsletter, CRM sync, consent gateways). When `false`, subscription-related views or logic may be skipped (e.g. the decorated view does nothing, or the post-login sync is not triggered). When `true`, subscription gateways and sign-up flows operate as configured; the receiver can trigger `subscribe_user_pk_task` after user login.

**Where it is used**

* **omnishop.utils.decorators.subscription\_activated** — Wraps a view so it runs only when `dynamic_settings.SUBSCRIPTION_ACTIVE` is true: `if dynamic_settings.SUBSCRIPTION_ACTIVE: return fn(self, *args, **kwargs)`; otherwise the wrapped function is not called.
* **omnishop.users.receivers.user\_subscription\_receiver\_on\_save** — On user save (not create) when `instance.last_login` is set: `if dynamic_settings.SUBSCRIPTION_ACTIVE and not created and instance.last_login:` then `subscribe_user_pk_task.delay(instance.pk)` is enqueued to sync the user with subscription gateways.

Key: `SUBSCRIPTION_ACTIVE`\
Type: boolean\
Default: `getattr(settings, "SUBSCRIPTION_ACTIVE", False)`

**Example**

* `true` — subscription views and post-login subscription sync are active.
* `false` — subscription flows are disabled (default).

### **32. Favourite SKU is Active**

Controls which product identifier is used when listing or deduplicating favourite (wishlist) products. When `true`, the favourite list is distinct by `product_id` (one row per product ID). When `false`, it is distinct by `product__base_code` (one row per base code, so variants can be grouped). Affects both the `order_by`/`distinct()` clause and the meaning of "same product" in the favourites list.

**Where it is used**

* **omnishop.wishlists.resources.views.FavouriteProductViewSet.list** — `favourite_sku_is_active = dynamic_settings.FAVOURITE_SKU_IS_ACTIVE`; then `distinct_query = 'product_id' if favourite_sku_is_active else 'product__base_code'`; the queryset is ordered and distinct by that field so the list shows one entry per product ID or per base code.

Key: `FAVOURITE_SKU_IS_ACTIVE`\
Type: boolean\
Default: from Django settings

**Example**

* `true` — favourites are keyed by `product_id` (each variant counts separately).
* `false` — favourites are keyed by `base_code` (variants of the same base product grouped).

### **33. Max Product per User Collection**

Maximum number of products allowed in a single user collection (e.g. a wishlist or custom list). When the user adds an item, the serializer counts existing items in that collection; if `total_products >= MAX_PRODUCT_PER_USER_COLLECTION`, a validation error is raised and the add is rejected. The error message can include the limit (e.g. "You can not add more than {0} products to collection.").

**Where it is used**

* **omnishop.wishlists.serializers.UserCollectionItemSerializer.validate** — `user_collection_max_product = dynamic_settings.MAX_PRODUCT_PER_USER_COLLECTION`; `total_products = self.Meta.model.objects.filter(usercollection=attrs['usercollection']).count()`; if `total_products >= user_collection_max_product` then `ValidationError` is raised with the configured message.

Key: `MAX_PRODUCT_PER_USER_COLLECTION`\
Type: number (int)\
Default: from Django settings (often 50 in comments)

**Example**

* `50` — up to 50 products per collection.
* `100` — more per list. Use a lower value (e.g. `2`) in tests to assert the limit.

### **34. AddressPhoneMinLength**

Minimum number of characters required for the address phone number in the Address serializer. The phone field is validated with Django's `MinLengthValidator(ADDRESS_PHONE_MIN_LENGTH)`; values shorter than this are rejected. Use it to enforce a minimum length (e.g. 11 for Turkish mobile) without tying to a specific regex.

**Where it is used**

* **omnishop.address.resources.serializers** — The address phone field has a validator: `MinLengthValidator(dynamic_settings.ADDRESS_PHONE_MIN_LENGTH)(value)` (or equivalent via `lambda value: MinLengthValidator(dynamic_settings.ADDRESS_PHONE_MIN_LENGTH)(value)`). Applied when validating address data (e.g. checkout or profile address).

Key: `ADDRESS_PHONE_MIN_LENGTH`\
Type: number (int)\
Default: 11

**Example**

* `11` — Turkish mobile length (05xxxxxxxxx).
* `10` — Shorter format. Should be consistent with `USER_PHONE_REGEX` if both are used.

### **35. CheckoutWithTokenAllowAnonymous**

When `true`, anonymous users can open the checkout-with-token flow (e.g. from a QR code or one-time link): the view allows access when there is no user from the token but the session exists and this setting is true. When `false`, token checkout requires an authenticated user from the token; otherwise 404 or "not allowed" is returned. Also controls whether checkout URLs with token are generated for anonymous sessions (e.g. in-store flow).

**Where it is used**

* **omnishop.orders.views.CheckoutWithTokenView\.get** — `anonymous_allowed = dynamic_settings.CHECKOUT_WITH_TOKEN_ALLOW_ANONYMOUS`; if `not user and (not anonymous_allowed or not getattr(request.session, "session_key", None))` then `raise Http404`. So anonymous access is allowed only when this is true and a session exists.
* **omnishop.orders.service** — In `get_checkout_urls_with_token`: `is_anonymous_allowed = dj_dynamic_settings.CHECKOUT_WITH_TOKEN_ALLOW_ANONYMOUS`; `_is_user_allowed(request, is_anonymous_allowed)` decides whether to return checkout URL and token; the query string is built with `is_anonymous_allowed` so the frontend can show the right behaviour.

Key: `CHECKOUT_WITH_TOKEN_ALLOW_ANONYMOUS`\
Type: boolean\
Default: false

**Example**

* `true` — QR/one-time-link checkout works for guest users (with session).
* `false` — only logged-in user from token can use the link (default).

### **36. OrdersRefundableDays**

Number of days after order (or order item) placement during which the order is eligible for return/refund. Exposed as a property on the order and on order items so refund/return logic can decide whether to allow the action. Past this window, return or refund options are typically disabled. `null` means no day-based limit is enforced by this setting (downstream logic may still allow or deny).

**Where it is used**

* **omnishop.orders.models.Order.refundable\_days** — `return dynamic_settings.ORDERS_REFUNDABLE_DAYS`.
* **omnishop.orders.models.OrderItem.refundable\_days** — `return dynamic_settings.ORDERS_REFUNDABLE_DAYS`. Refund/return code compares order age (e.g. days since creation) to this value to decide eligibility.

Key: `ORDERS_REFUNDABLE_DAYS`\
Type: number (int) or null\
Default: `getattr(settings, "ORDERS_REFUNDABLE_DAYS", None)`

**Example**

* `30` — returns/refunds allowed within 30 days of order.
* `14` — shorter window.
* `null` — no day limit from this setting; business logic may still restrict returns.

### **37. GiftBoxConfigurations**

Config for the optional gift-box product in checkout: **sku** — product SKU used to resolve the gift box product and its price; **is\_active** — boolean to show/hide the gift box step and offer. When `is_active` is true, the checkout index page and gift box page use this config; the `GiftBox` helper is instantiated with `**dj_dynamic_settings.GIFT_BOX_CONFIGURATIONS` so it can load price and attach the gift box to the pre\_order.

**Where it is used**

* **omnishop.orders.pages.index** — `gift_box_conf = dj_dynamic_settings.GIFT_BOX_CONFIGURATIONS`; context includes `'has_gift_box': gift_box_conf.get('is_active') or False`.
* **omnishop.orders.pages.gift\_box** — `GiftBoxPreCondition.is_valid` and `GiftBoxPage.is_gift_box_active` use `gift_box_conf.get('is_active')`. `GiftBoxPage.get_page_context` and `process_pre_order` build a `GiftBox` with `GiftBox(..., **dj_dynamic_settings.GIFT_BOX_CONFIGURATIONS)` to get price and attach to pre\_order.

Key: `GIFT_BOX_CONFIGURATIONS`\
Type: object (keys: `sku`, `is_active`)\
Default: `{"sku": null, "is_active": false}`

**Example**

```json
{"sku": "GIFT-BOX-01", "is_active": true}
```

* `sku` must match a product SKU in the catalog; that product is used as the gift box and for pricing.

### **38. PrettyUrlIncludeDefaultLanguage**

When `true`, pretty URL generation includes the default/site language in the path even when the language is the default (e.g. `/en/products/...` for default English). When `false`, the default language is represented with a "none" language code so the path can omit the language prefix (e.g. `/products/...` for default). Used inside the prettyurls language decorator when building `language_code` for URL generation.

**Where it is used**

* **omnishop.prettyurls.generators** — In the decorator that injects language into URL generation: `include_default_language = dynamic_settings.PRETTY_URL_INCLUDE_DEFAULT_LANGUAGE`; if `language == site_language and not include_default_language` then `language_code = none_language`, else `language_code = get_language_prefix(language).lower()`. So the generated path includes or omits the language segment based on this setting.

Key: `PRETTY_URL_INCLUDE_DEFAULT_LANGUAGE`\
Type: boolean\
Default: false

**Example**

* `true` — default language still appears in URL (e.g. `/en/...`).
* `false` — default language has no prefix in the path (e.g. `/...`).

### **39. ForceNotRedirectForI18N**

When `true`, the I18N middleware skips the logic that redirects the user to a URL with the correct language prefix (e.g. when `PRETTY_URL_MULTI_LANGUAGE` is true and the path language does not match the active language or site default). When `false`, that redirect runs so users are sent to the localized path. Use `true` to disable automatic language redirects (e.g. for API or when you handle language elsewhere).

**Where it is used**

* **omnicore.utils.middleware** — The condition for performing the language redirect is `getattr(settings, 'PRETTY_URL_MULTI_LANGUAGE', False) and not getattr(dynamic_settings, 'FORCE_NOT_REDIRECT_FOR_I18N', False)`. So when `FORCE_NOT_REDIRECT_FOR_I18N` is true, the block that calls `get_localized_url` and `redirect_response` is skipped and no redirect is made.

Key: `FORCE_NOT_REDIRECT_FOR_I18N`\
Type: boolean\
Default: false

**Example**

* `true` — no automatic redirect to language-prefixed URL.
* `false` — middleware may redirect to the localized URL when multi-language pretty URLs are enabled.

### **40. RetailStoreFilterStrategy**

Dotted path to the strategy class that filters which retail stores (e.g. click-and-collect) are shown for a basket. Strategies: **FullySatisfiedQuantityFilterStrategy** — only stores that can fulfill the full basket quantity; **PartiallySatisfiedQuantityFilterStrategy** — stores that can fulfill at least part; **NotSatisfiedQuantityFilterStrategy** — all stores regardless of stock.

**Where it is used**

* **omnishop.products.backend.gateways.service** — In `get_strategy()`: `klass = import_string(dynamic_settings.RETAIL_STORE_FILTER_STRATEGY)()`; the returned strategy instance is used when resolving store list for the basket (e.g. for pickup or click-and-collect).

Key: `RETAIL_STORE_FILTER_STRATEGY`\
Type: string (dotted path)\
Default: `NotSatisfiedQuantityFilterStrategy`

**Choices**

* `omnishop.products.backend.gateways.filter_strategies.FullySatisfiedQuantityFilterStrategy` — only stores that fully satisfy basket quantity.
* `omnishop.products.backend.gateways.filter_strategies.PartiallySatisfiedQuantityFilterStrategy` — stores that partially satisfy.
* `omnishop.products.backend.gateways.filter_strategies.NotSatisfiedQuantityFilterStrategy` — all stores (no stock filter).

**Example**

Use `FullySatisfiedQuantityFilterStrategy` to show only stores that can fulfill the entire basket; use `NotSatisfiedQuantityFilterStrategy` to show all stores regardless of stock.

### **41. PromotionGatewayActiveSettings**

When `true`, the promotion gateway is considered active and virtual offers (e.g. from brand-specific integrations like Ayakkabı Dünyası) can be queried and applied. When `false`, the gateway is off and no external promotion queries run.

**Where it is used**

* **omnishop.promotions.backend.gateways.service** — In the gateway service: `is_active` is `True` only when `dynamic_settings.PROMOTION_GATEWAY_ACTIVE` and `self.promotion_gateways` are both truthy; `query()` returns an empty list if `not self.is_active`, otherwise it fetches offers from configured promotion gateways.

Key: `PROMOTION_GATEWAY_ACTIVE`\
Type: boolean\
Default: false

**Example**

Set to `True` when using a promotion gateway (e.g. Erencard, Softtouch, Hitit); leave `False` if you do not use external promotion gateways.

### **42. SmsGateways**

Dictionary mapping SMS gateway names to their configuration objects. Each entry typically has `klass` (dotted path to the backend class) and `conf` (gateway-specific options, e.g. `verify_message` for OTP text). The gateway actually used is selected by `SMS_GATEWAY`. Used for sending SMS (OTP, notifications).

**Where it is used**

* **omnishop.orders.service.SmsService** — `_get_sms_gateway_settings(sms_gateway)` returns `dj_dynamic_settings.SMS_GATEWAYS.get(sms_gateway)`; `_get_gateway()` uses that config to instantiate the backend via `import_string(gateway_config.get('klass'))(**gateway_config.get('conf'))`. When `SMS_GATEWAY` is `"console"`, a built-in console backend is used instead.

Key: `SMS_GATEWAYS`\
Type: object\
Default: `{"console": {"conf": {"verify_message": "Verification code: {}"}}}`

**Example**

```json
{
  "console": {"conf": {"verify_message": "Verification code: {}"}},
  "provider_slug": {"klass": "omnishop.orders.backends.SomeSmsBackend", "conf": {"api_key": "..."}}
}
```

### **43. SmsGateway**

Name of the active SMS gateway. If set to `"console"`, SMS/OTP are logged to the console (no real sending). Otherwise this value must exist as a key in `SMS_GATEWAYS`; that entry's `klass` and `conf` are used to send SMS.

**Where it is used**

* **omnishop.orders.service.SmsService** — `_get_gateway()` reads `dj_dynamic_settings.SMS_GATEWAY`; when it is `"console"` it returns `SmsBackend`; otherwise it looks up the config in `SMS_GATEWAYS` and instantiates the configured backend. Used by `send_sms`, `send_otp`, and `verify_otp`.

Key: `SMS_GATEWAY`\
Type: string\
Default: "console"

**Example**

Use `"console"` for local/testing; use the same slug as a key in `SMS_GATEWAYS` (e.g. `"provider_slug"`) for production SMS.

### **44. ReCaptchaSiteKey**

Google reCAPTCHA v2 site key (public key) shown in the browser. The frontend uses it to render the reCAPTCHA widget; the user response token is then verified on the server using `RECAPTCHA_SECRET_KEY`.

**Where it is used**

* **omnishop.users.forms.CaptchaField** — `site_key = property(lambda self: dj_dynamic_settings.RECAPTCHA_SITE_KEY)`; passed to the widget as `data-sitekey`. Both `RECAPTCHA_SITE_KEY` and `RECAPTCHA_SECRET_KEY` must be set or the field raises `ImproperlyConfigured`.

Key: `RECAPTCHA_SITE_KEY`\
Type: string\
Default: ''

**Example**

Set to the site key from Google reCAPTCHA admin (e.g. `"6Lc..."`) when using reCAPTCHA on registration or contact forms.

### **45. ReCaptchaSecretKey**

Google reCAPTCHA secret key used for server-side verification. The backend sends this with the user's response token to `https://www.google.com/recaptcha/api/siteverify` to validate the captcha.

**Where it is used**

* **omnishop.users.forms.CaptchaField** — `secret_key` property and `validate()`: POST to siteverify with `secret` and `response`; both keys must be defined for the field to work.

Key: `RECAPTCHA_SECRET_KEY`\
Type: string\
Default: ''

**Example**

Set to the secret key from the same reCAPTCHA admin; keep it server-side only and never expose it in the frontend.

### **46. SelfAnonymizationEnabled**

When `true`, authenticated users can trigger self-anonymization (account data anonymized and session logged out). When `false`, the self-anonymization API returns 403.

**Where it is used**

* **omnishop.users.views** — In the self-anonymization `patch` handler: `if not dynamic_settings.SELF_ANONYMIZATION_ENABLED` then `raise PermissionDenied()`; otherwise the service anonymizes the user and logs them out.

Key: `SELF_ANONYMIZATION_ENABLED`\
Type: boolean\
Default: false

**Example**

Enable when you offer a "Delete my data" or "Anonymize my account" self-service option; disable if only admins should perform anonymization.

### **47. ContactUsSendUser**

When `true`, the Contact Us form submitter's email is added to the email's **To** list (in addition to `CONTACT_US_EMAIL_TO`). When `false`, only the configured recipients receive the email.

**Where it is used**

* **omnishop.users.service** — When sending the Contact Us email: `if dj_dynamic_settings.CONTACT_US_SEND_USER and email:` then `to += (email,)`. Reply-To is set separately by `CONTACT_US_REPLY_TO_ONLY_USER`.

Key: `CONTACT_US_SEND_USER`\
Type: boolean\
Default: false

**Example**

Set to `true` so the user gets a copy of the message or is visible as a recipient; set to `false` to send only to internal addresses.

### **48. ContactUsReplyToOnlyUser**

When `true`, the Contact Us email's **Reply-To** is set only to the submitter's email so that "Reply" goes to the user. When `false`, Reply-To is the same as the To list.

**Where it is used**

* **omnishop.users.service** — When building the email: `if dj_dynamic_settings.CONTACT_US_REPLY_TO_ONLY_USER and email:` then `reply_to = (email,)`; otherwise `reply_to = tuple(to)`.

Key: `CONTACT_US_REPLY_TO_ONLY_USER`\
Type: boolean\
Default: false

### **49. KvkkGateway**

Configuration for the KVKK (GDPR) consent gateway. When set, the system can sync consent/permission changes (SMS, email, call, ETK share) to an external gateway for verified users. Structure is validated by `KvkkGatewaySerializer` (typically `name` and `conf` or similar fields).

**Where it is used**

* **omnishop.users.service** — In `update_kvkk_permissions()`: `KVKK_GATEWAY = dj_dynamic_settings.KVKK_GATEWAY`; if `not KVKK_GATEWAY or not verified_user` the method returns without calling the gateway; otherwise it calls `update_permissions()` with the gateway (e.g. for Mobildev).

Key: `KVKK_GATEWAY`\
Type: object\
Default: null

**Example**

Configure when using a KVKK/consent provider (e.g. Mobildev); leave null if you do not sync consent to an external gateway.

### **50. DefaultCurrency**

Default currency code for the shop (e.g. `try`, `usd`). Must be one of the values in `AVAILABLE_CURRENCIES`. Used for pricing, basket, and order defaults when no other currency is selected.

**Where it is used**

* **omnishop.users.settings.conf** — Validated via `FunctionThroughValidator`: value must be in `dynamic_settings.AVAILABLE_CURRENCIES`. `AVAILABLE_CURRENCIES` in turn validates that it contains `DEFAULT_CURRENCY`.
* **Orders, promotions, wishlists, segmentation, loyalty** — Tests and services use it as the default currency (e.g. `override_dynamic_settings(DEFAULT_CURRENCY="try")`, basket/order currency fallback).

Key: `DEFAULT_CURRENCY`\
Type: string\
Default: from Django settings (e.g. `DEFAULT_CURRENCY_TYPE`)

**Example**

Set to the primary store currency (e.g. `"try"`, `"usd"`); must appear in `AVAILABLE_CURRENCIES`.

### **51. MobildevConf**

Mobildev integration configuration (validated by `MobildevConfSerializer`). Typically holds a `conf` object with credentials and options used to communicate with the Mobildev API for KVKK/consent, user sync, or subscription features.

**Where it is used**

* **omnishop.libs.kvkk\_gateways.mobildev.service.MobilDevService** — `gateway` property builds the Mobildev gateway with `MobilDev(**settings.MOBILDEV_CONF['conf'])`; used for sending confirmation codes, blacklist, and permission sync.
* **omnishop.users.tests.test\_tasks** — KVKK tasks may override with Mobildev config for testing.

Key: `MOBILDEV_CONF`\
Type: object\
Default: `{}` (from Django `MOBILDEV_CONF`)

**Example**

Configure when using Mobildev for KVKK/consent or user sync; structure must match `MobildevConfSerializer` (e.g. `conf` with API credentials).

### **52. ShouldSendVerificationEmail**

When `true`, the system sends a verification email after guest user registration (and when applicable after order). When `false`, `EMAIL_VERIFICATION` is forced to `None` so no verification email is sent.

**Where it is used**

* **omnishop.users.service** — When creating a guest user: `should_send_mail = dj_dynamic_settings.SHOULD_SEND_VERIFICATION_EMAIL`; if not set, `email_verification` is set to `None` so allauth does not send the verification email.

Key: `SHOULD_SEND_VERIFICATION_EMAIL`\
Type: boolean\
Default: true

**Example**

Set to `false` to disable verification emails for guest signups; set to `true` for normal verification flow.

### **53. UniqueValidatorPhoneMessage**

Error message shown when phone uniqueness validation fails (e.g. user or loyalty card registration with a phone number that already exists).

**Where it is used**

* **omnishop.users.resources.serializers** — Raised as `serializers.ValidationError(_(dj_dynamic_settings.UNIQUE_VALIDATOR_PHONE_MESSAGE))` when phone is duplicate (e.g. in user profile, registration, or loyalty serializers).
* **omnishop.loyaltycard.resources.serializers** — Same message when phone uniqueness fails for loyalty card.

Key: `UNIQUE_VALIDATOR_PHONE_MESSAGE`\
Type: string\
Default: `""`

**Example**

e.g. `"This phone number is already registered."` or a localized message key.

### **53.1. User Profile Validations**

This configuration defines a list of validations that are run when a user profile is updated. Each entry specifies a validation class (by module path) and optional default keyword arguments passed to that validator.

Key: `USER_PROFILE_VALIDATIONS`\
Type: array of objects\
Default: \[]

Each item in the array must have:

* **klass** (string): The dotted path of the validation helper. One of:
  * `omnishop.users.helpers.check_orders` — Check Order. Validates against existing orders; use **defaults.status** to restrict to a specific order status (e.g. `"350"` checks only orders with status 350 — confirmation\_waiting). See **Order status codes** below.
  * `omnishop.users.helpers.check_unique_phone` — Check Unique Phone
* **defaults** (object): Optional key-value arguments passed to the validation helper.

#### **Order status codes**

When using `check_orders`, the **defaults.status** value refers to one of the following order statuses:

| Code | Enum name              | Description                    |
| ---- | ---------------------- | ------------------------------ |
| 50   | cancellation\_waiting  | Cancellation waiting           |
| 100  | cancelled              | Cancelled                      |
| 200  | waiting                | Waiting                        |
| 300  | payment\_waiting       | Payment waiting                |
| 350  | confirmation\_waiting  | Confirmation waiting           |
| 400  | approved               | Approved                       |
| 450  | preparing              | Preparing                      |
| 500  | shipped                | Shipped                        |
| 510  | shipped\_and\_informed | Shipped and informed           |
| 520  | ready\_for\_pickup     | Ready for pickup               |
| 540  | attempted\_delivery    | Attempted delivery             |
| 544  | review\_started        | Review started                 |
| 545  | review\_waiting        | Review waiting                 |
| 546  | waiting\_for\_payment  | Waiting for payment (trade-in) |
| 547  | paid                   | Paid (trade-in)                |
| 550  | delivered              | Delivered                      |
| 600  | refunded               | Refunded                       |

Example:

```
[
    {
        "klass": "omnishop.users.helpers.check_unique_phone",
        "defaults": {}
    },
    {
        "klass": "omnishop.users.helpers.check_orders",
        "defaults": {
            "status": "350"
        }
    }
]
```

In the example above, `check_orders` with `"status": "350"` validates against orders that are in **confirmation\_waiting** status.

### **54. AddressSerializerField**

Per-country configuration for extra or customized fields on the address serializer. Structure: dict keyed by country code (e.g. `"tr"`, `"default"`), each value a list of field configs with `field`, optional `kwargs`, and optional `validators` (klass + kwargs). Used to add or override address fields and validators by country.

**Where it is used**

* **omnishop.address.resources.serializers.AddressSerializer** — In `__init__`: reads `dynamic_settings.ADDRESS_SERIALIZER_FIELDS`; selects the list by `country.code` or falls back to `"default"`; for each entry adds or updates the field (with optional validators). Also respects `ADDRESS_DISTRICT_REQUIRED` for district.

Key: `ADDRESS_SERIALIZER_FIELDS`\
Type: object (dict: country code to list of field configs)\
Default: `{"default": []}`

**Example**

`{"default": [], "tr": [{"field": "district", "kwargs": {"required": true}, "validators": [{"klass": "...", "kwargs": {"message": "..."}}]}]}`

### **55. TmpSapCRMMobildevActivationSetting**

When `true`, enables temporary logic that uses Mobildev user data (e.g. `mobildev_id` from user attributes) in SAP/Hana CRM flows. When `false`, that behaviour is disabled.

**Where it is used**

* **omnishop.libs.crm\_gateways.hana.service** — When syncing or building CRM payloads: `if getattr(dynamic_settings, "TMP_SAP_CRM_MOBILDEV_ACTIVATION_SETTING", False):` then e.g. `mobildev_id = user.attributes.get("mobildev_id")` is used.
* **omnishop.libs.crm\_gateways.hana.gateway** — Same check for Mobildev activation in gateway code.

Key: `TMP_SAP_CRM_MOBILDEV_ACTIVATION_SETTING`\
Type: boolean\
Default: false

**Example**

Set to `true` only when SAP CRM integration is configured to use Mobildev IDs; treat as temporary until a permanent integration is in place.

### **56. CRMConf**

CRM integration configuration. Typically a dict with a `conf` key holding gateway-specific options (e.g. SAP/Hana CRM credentials and endpoints). Used when syncing orders or customers to an external CRM.

**Where it is used**

* **omnishop.libs.crm\_gateways.hana.service** — Gateway is built with `HanaCrmGateway(**dynamic_settings.CRM_CONF['conf'])`; used for order/customer sync to Hana CRM.

Key: `CRM_CONF`\
Type: object\
Default: `{}` (from Django `CRM_CONF`)

**Example**

Structure must match what the CRM gateway expects (e.g. `{"conf": {"base_url": "...", ...}}` for Hana).

### **57. OmnitronAPI**

Omnitron API credentials and endpoints (validated by `OmnitronAPISerializer`). Used by the Omnitron API client for auth, orders, cancellation, shipping cost, etc.

**Where it is used**

* **omnishop.libs.api\_clients.omnitron.client.OmnitronApiClient** — `get_data()` reads `getattr(dynamic_settings, 'OMNITRON_API', {})` for `host`, `oms_host`, `auth_url` (derived from host), `username`, `passwd`, `email`, `channel_id`. Used for authentication and all API requests (orders, cancellation, bank account, etc.).
* **omnishop.orders.service**, **omnishop.orders.resources.views**, **omnishop.orders.pages.base**, **omnishop.orders.tasks** — Use `OmnitronApiClient()` which relies on this setting.

Key: `OMNITRON_API`\
Type: object\
Default: `{}` (from Django `OMNITRON_API`)

**Example**

e.g. `{"host": "https://...", "username": "...", "passwd": "...", "email": "..."}`; may include `oms_host`, `channel_id` depending on serializer.

### **58. SecuritycopIpBanTimeout**

Duration in seconds that an IP ban lasts (e.g. after too many failed login or suspicious attempts). After this time, the IP is no longer treated as banned and the cache key expires.

**Where it is used**

* **omnicore\_project.omnicore.security.services.SecurityCop** — In `catch()`: `_IP_BAN_TIMEOUT = getattr(dynamic_settings, "SECURITYCOP_IP_BAN_TIMEOUT", 60 * 60 * 6)`; that value is used as the cache TTL when marking the client IP as needing verification (`cache.set(cache_key, True, _IP_BAN_TIMEOUT)`). Used to decide how long the IP remains "suspicious" before the ban expires.

Key: `SECURITYCOP_IP_BAN_TIMEOUT`\
Type: int\
Default: 21600 (6 hours)

**Example**

Use a smaller value (e.g. 3600) for shorter bans, or a larger one for stricter security.

### **59. BasketMaxItemCount**

Maximum total quantity of items allowed in a single basket (sum of all line quantities). When exceeded, the basket condition raises `BasketMaxItemCount` and adding/updating items is blocked.

**Where it is used**

* **omnishop.baskets.conditions.basket\_item\_max\_count** — `basket_max_item_count = dj_dynamic_settings.BASKET_MAX_ITEM_COUNT`; if `basket.get_total_quantity() > basket_max_item_count` then `raise exceptions.BasketMaxItemCount(params=(basket_max_item_count,))`. Used when evaluating basket conditions (e.g. before adding to cart).

Key: `BASKET_MAX_ITEM_COUNT`\
Type: int\
Default: 80

**Example**

Lower the value (e.g. 10) to enforce a stricter cap; raise it for high-quantity B2B baskets.

### **60. GroupedProductPriceSelector**

Dotted path to the callable that selects the displayed price for a grouped product (e.g. min, max, or sum of variant prices).

**Where it is used**

* **omnishop.catalogs.selector.get\_group\_product\_price\_selector** — Returns `import_string(dynamic_settings.GROUPED_PRODUCT_PRICE_SELECTOR)`; that callable is used when resolving the price to show for a grouped product (e.g. in catalog/price logic).

Key: `GROUPED_PRODUCT_PRICE_SELECTOR`\
Type: string (dotted path)\
Default: `omnishop.catalogs.selector.group_product_min_price_selector`

**Example**

* `omnishop.catalogs.selector.group_product_min_price_selector` — show minimum variant price.
* `omnishop.catalogs.selector.group_product_max_price_selector` — show maximum variant price.
* `omnishop.catalogs.selector.group_product_sum_price_selector` — show sum of variant prices.

### **61. ListSimpleInsteadOfMeta**

When `true`, a simple (child) product can be chosen as the listable product for a group instead of the meta (parent). When `false`, the listable product is the meta product. Affects which product is used for listing/display and URL generation.

**Where it is used**

* **omnishop.products.service** — In product creation and meta creation: `list_simple = dynamic_settings.LIST_SIMPLE_INSTEAD_OF_META`; when true, listable is resolved from children with `is_listable=True`; when false, `product_listable = parent`. Also in `_create_product_meta`: `is_listable = not is_listable and not list_simple`.

Key: `LIST_SIMPLE_INSTEAD_OF_META`\
Type: boolean\
Default: false

**Example**

Set to `true` when you want the listable product in listing pages to be a specific variant (simple) rather than the group (meta).

### **62. NoReplyEmail**

Sender (From) address used for system emails that should not receive replies (e.g. order confirmations, shipping notifications, verification codes). Validated as a valid email.

**Where it is used**

* **omnishop.users.service** — Verification code email: `from_email = dj_dynamic_settings.NO_REPLY_EMAIL`. Other code paths use `settings.NO_REPLY_EMAIL` or `getattr(settings, 'NO_REPLY_EMAIL', 'noreply@akinon.com')` for order/shipping/contact emails (Django settings; dynamic value can override at runtime if wired).
* **omnishop.orders.service** — Multiple email sends use `from_email=settings.NO_REPLY_EMAIL` for order and shipping emails.

Key: `NO_REPLY_EMAIL`\
Type: string\
Default: <noreply@akinon.com>

**Example**

Set to your no-reply address (e.g. `noreply@yourdomain.com`).

### **63. EmailSmtpConfiguration**

SMTP connection settings for sending email (validated by `EmailSmtpConfigurationSerializer`). Typically includes host, port, username, password, use\_tls, use\_ssl, ssl\_certfile, ssl\_keyfile, timeout. Passed to the Django SMTP backend so sending uses these values instead of or in addition to Django `EMAIL_*` settings.

**Where it is used**

* **omnishop.cms.email.backends.smtp.EmailBackend** — In `__init__`: `conf = dynamic_settings.EMAIL_SMTP_CONFIGURATION` then `conf.update(**kwargs)` and `super().__init__(**conf)`; the backend is thus configured from this setting.

Key: `EMAIL_SMTP_CONFIGURATION`\
Type: object\
Default: derived from Django `EMAIL_HOST`, `EMAIL_PORT`, etc.

**Example**

Override at runtime for different SMTP credentials or TLS/SSL without changing Django settings.

### **64. PushNotificationActive**

When `true`, push notification dispatch is enabled (order created, status changed, basket offer, etc.). When `false`, the notification decorator skips sending.

**Where it is used**

* **omnishop.libs.push\_notification\_gateways.service.NotificationDispatchService** — Methods are decorated with `@notification_activated`; the decorator checks `getattr(dynamic_settings, 'PUSH_NOTIFICATION_ACTIVE', False)` and only runs the method (e.g. `notify_order_created`, `notify_order_status_changed`) when true.

Key: `PUSH_NOTIFICATION_ACTIVE`\
Type: boolean\
Default: false

**Example**

Set to `true` when using a push gateway (EuroMessage, Webinstats, etc.); ensure `PUSH_NOTIFICATION_GATEWAY` and `PUSH_NOTIFICATION_GATEWAYS` are configured.

### **65. PushNotificationGateway**

Name of the active push notification gateway. Must match a key in `PUSH_NOTIFICATION_GATEWAYS`. The gateway is instantiated with that entry's `klass` and `conf`.

**Where it is used**

* **omnishop.users.service.PushNotificationService** — `gateway` property: `gateway_name = dj_dynamic_settings.PUSH_NOTIFICATION_GATEWAY`, `gateway_settings = dj_dynamic_settings.PUSH_NOTIFICATION_GATEWAYS.get(gateway_name)`; then `klass = import_string(gateway_settings['klass'])`, `gateway = klass(**gateway_settings['conf'])`. Used for `notify_order_created`, `notify_order_status_changed`, etc.

Key: `PUSH_NOTIFICATION_GATEWAY`\
Type: string\
Default: null (from Django `PUSH_NOTIFICATION_GATEWAY`)

**Example**

e.g. `"euro_message"` or `"webinstats"`; must exist as a key in `PUSH_NOTIFICATION_GATEWAYS`.

### **66. PushNotificationGateways**

Dictionary mapping push notification gateway names to their configuration (typically `klass` and `conf`). The active gateway is selected by `PUSH_NOTIFICATION_GATEWAY`.

**Where it is used**

* **omnishop.users.service.PushNotificationService** — `gateway` property looks up `dj_dynamic_settings.PUSH_NOTIFICATION_GATEWAYS.get(gateway_name)` and instantiates the backend from `klass` and `conf`.

Key: `PUSH_NOTIFICATION_GATEWAYS`\
Type: object\
Default: `{}` (from Django `PUSH_NOTIFICATION_GATEWAYS`)

**Example**

e.g. `{"euro_message": {"klass": "...", "conf": {...}}, "webinstats": {...}}`.

### **67. IgnoreDeletedItemsSignalConfig**

List of model labels (e.g. `app_label.ModelName`) for which delete signals are ignored. When a model in this list is deleted, a `DeletedItem` record is not created in the datawarehouse (used to avoid syncing certain internal deletions).

**Where it is used**

* **omnishop.datawarehouse.receivers.item\_delete\_receiver** — On delete: `if sender._meta.label not in dynamic_settings.IGNORE_DELETED_ITEMS_SIGNAL` then `DeletedItem.objects.create(...)`; otherwise the deletion is ignored for the datawarehouse.

Key: `IGNORE_DELETED_ITEMS_SIGNAL`\
Type: array (list of model labels)\
Default: from Django `IGNORE_DELETED_ITEMS_SIGNAL` (e.g. includes AuditEvent, Session, DeletedItem)

**Example**

Add model labels (e.g. `"sessions.Session"`) to avoid creating DeletedItem records for those models.

### **68. ShippingProvidersSetting**

Configuration for shipping providers (carriers). Keys are provider identifiers (e.g. carrier enum values); each entry typically has `conf` with provider-specific options (e.g. tracking URL generation). Used when resolving tracking URLs and carrier behaviour.

**Where it is used**

* **omnicore\_project.omnicore.orders.enums** — In `get_tracking_url()`: `provider = settings.SHIPPING_PROVIDERS.get(self.value)`; `conf` is used e.g. for `generate_tracking_url` on the extension provider.
* **omnishop.orders.settings.conf** — Setting defined here; tests and shippings use it for provider config.

Key: `SHIPPING_PROVIDERS`\
Type: object\
Default: `{}` (from Django `SHIPPING_PROVIDERS`)

**Example**

Dict keyed by carrier identifier with `conf` (and optionally other keys) per provider.

### **69. EasyReturnPackageStrategy**

Dotted path to the strategy class that determines easy-return package behaviour (e.g. rule-based vs selection-based). The strategy is used to build easy-return objects for order item cancellation.

**Where it is used**

* **omnishop.orders.strategies.get\_easy\_return\_package\_strategy** — `klass = import_string(dynamic_settings.EASY_RETURN_PACKAGE_STRATEGY)`; returns an instance used for `get_easy_return_object` / `get_easy_return_objects`.
* **omnishop.shippings.resources.views** — Appointment-date view checks `dynamic_settings.EASY_RETURN_PACKAGE_STRATEGY != EasyReturnPackageStrategyType.selection_based_easy_return_package_strategy.value` and returns 400 when the strategy is not selection-based (appointment dates only supported for that strategy).

Key: `EASY_RETURN_PACKAGE_STRATEGY`\
Type: string (dotted path)\
Default: `omnishop.orders.strategies.RuleBasedEasyReturnPackageStrategy`

**Example**

Use `omnishop.orders.strategies.SelectionBasedEasyReturnPackageStrategy` when appointment-date selection is required for easy return.

### **70. OrderNumberGeneratorConf**

This configuration customizes the order number format by defining a **pattern** (with optional prefix/postfix) and **numbers\_length**. Only used when the order number generator is set to a function that reads this conf (e.g. `generate_order_number_v2`).

**Where it is used**

* **omnishop.orders.generator.generate\_order\_number\_v2** — `conf = dynamic_settings.ORDER_NUMBER_GENERATOR_CONF`; uses `conf["pattern"]` and `conf["numbers_length"]` to build the order number (time-based + increment + random, formatted by pattern). The Django env var `ORDER_NUMBER_GENERATOR` must point to this (or another) generator for the conf to apply.

Key: `ORDER_NUMBER_GENERATOR_CONF`\
Type: object\
Default: { "pattern": "{numbers}", "numbers\_length": 12 }

**Parameters**

* **pattern**\
  Defines the format of the order number.\
  The `{numbers}` placeholder represents the auto-generated numeric value.\
  Static text can be added before or after this placeholder to define a prefix and/or postfix.
* **numbers\_length**\
  Specifies the length of the numeric value generated for the `{numbers}` placeholder.

**Example 1:**\
{“pattern”: “ON\_{numbers}”, “numbers\_length”: 12}\
Output: “ON\_123456789012”

In this example, the order number starts with the `ON_` prefix followed by a 12-digit numeric value.

**Example 2:**\
{“pattern”: “{numbers}AK”, “numbers\_length”: 5}\
Output: “12345AK”

In this example, the order number starts with a 5-digit numeric value and ends with the `AK` postfix.

### **71. SendShippingNotificationsPartially**

When `true`, shipping notifications (email, SMS, push) can be sent per order item when that item is shipped (partial shipping). When `false`, the full-order shipping notification is used (sent once when the order reaches shipped with a tracking number).

**Where it is used**

* **omnishop.orders.actions** — `send_shipping_notifications` is registered only when `not dynamic_settings.SEND_SHIPPING_NOTIFICATIONS_PARTIALLY` (full-order flow). `send_partial_shipping_notifications` is registered when `dynamic_settings.SEND_SHIPPING_NOTIFICATIONS_PARTIALLY` and runs per order item (shipped + tracking number), calling `OrderShippingNotificationService.send_partial_shipping_mail/sms/notification`.

Key: `SEND_SHIPPING_NOTIFICATIONS_PARTIALLY`\
Type: boolean\
Default: False

**Example**

Set to `true` when you ship items separately and want one notification per shipped item; set to `false` for one notification per order.

### **72. SendShippingDeliveredNotificationsPartially**

When `true`, delivered notifications can be sent per order item when that item is delivered. When `false`, the full-order delivered notification is used (once when the order reaches delivered).

**Where it is used**

* **omnishop.orders.actions** — `send_order_delivery_notifications` runs only when `not dynamic_settings.SEND_SHIPPING_DELIVERED_NOTIFICATIONS_PARTIALLY` (full-order delivered). `send_partial_shipping_delivered_notifications` runs when `dynamic_settings.SEND_SHIPPING_DELIVERED_NOTIFICATIONS_PARTIALLY` and calls `OrderShippingNotificationService.send_partial_shipping_delivered_mail` etc. per item.

Key: `SEND_SHIPPING_DELIVERED_NOTIFICATIONS_PARTIALLY`\
Type: boolean\
Default: False

**Example**

Set to `true` for per-item delivered emails/SMS; set to `false` for one delivered notification per order.

### **73. ExtraCancelRules**

Extra rules for order cancellation. Supports `extra_direct_cancellable_statuses` (list of statuses to add to cancellable), `has_invoice` (block cancel if order has invoice), and `hour_gap` (time window: `is_active`, `time` in minutes) to block cancellation after a delay.

**Where it is used**

* **omnishop.orders.service.OrderService** — In `get_cancellable_statuses()`: `extra_cancel_rules = getattr(dj_dynamic_settings, 'EXTRA_CANCEL_RULES', {})`; extends `cancellable_order_statuses` with `extra_direct_cancellable_statuses`. In `validate_order_cancellation()`: checks `has_invoice` (raises if order has invoice number) and `hour_gap` (raises `OrderCancellationExpiredException` if past the time window).

Key: `EXTRA_CANCEL_RULES`\
Type: object\
Default: `{}`

**Example**

`{"extra_direct_cancellable_statuses": ["preparing"], "has_invoice": true, "hour_gap": {"is_active": true, "time": 60}}` to allow cancel in preparing, block if invoiced, and block 60 minutes after order creation.

### **74. CheckoutStockAvailableCheck**

When `true`, order creation (checkout) re-checks stock for each basket item and blocks creation if any item is not available to buy (e.g. `is_available_to_buy` fails for the requested quantity). When `false`, stock is not re-validated at checkout.

**Where it is used**

* **omnishop.orders.service.OrderService.create\_order** — `checkout_stock_available_check = getattr(dj_dynamic_settings, 'CHECKOUT_STOCK_AVAILABLE_CHECK', None)`; if true, for each basket item loads product stock and calls `basket_item.is_available_to_buy(basket_item.get_quantity_for_stock())`; if not available, order creation fails.

Key: `CHECKOUT_STOCK_AVAILABLE_CHECK`\
Type: boolean\
Default: false

**Example**

Set to `true` to avoid creating orders for out-of-stock items; set to `false` if stock is enforced elsewhere or not required.

### **75. SendRefundResultNotifications**

When `true`, after an order transitions to refunded the system sends refund result SMS and push notification to the user. When `false`, no refund result notification is sent.

**Where it is used**

* **omnishop.orders.actions** — `send_refund_result_notifications` is registered with condition `getattr(dynamic_settings, "SEND_REFUND_RESULT_NOTIFICATIONS", False)` and when order goes to `refunded`; it calls `OrderTaskService.send_refund_result_sms` and `send_refund_result_notification`.

Key: `SEND_REFUND_RESULT_NOTIFICATIONS`\
Type: boolean\
Default: false

**Example**

Set to `true` to notify users when their refund is processed.

### **76. PickupLocationProviderBaseUrl**

Base URL for the pickup location provider API (e.g. for click-and-collect). **Deprecated:** Prefer using `PICKUP_LOCATION_PROVIDER` (with `klass` and `conf`) instead.

**Where it is used**

* **omnishop.address.pickup\_locations.provider** — In the provider: `base_url = self.base_url or dynamic_settings.PICKUP_LOCATION_PROVIDER_BASE_URL`; used in `get_url()` to build API URLs for pickup locations. Also used when the provider is instantiated with conf that does not override `base_url`.

Key: `PICKUP_LOCATION_PROVIDER_BASE_URL`\
Type: string\
Default: `""`

**Example**

Set to the base URL of your pickup-location API (e.g. `https://api.example.com/pickup`); consider migrating to `PICKUP_LOCATION_PROVIDER`.

### **77. InstallmentFilters**

List of installment filter configs. Each entry has `klass` (dotted path to filter class) and `kwargs` (options). Filters are applied in order to reduce or format installment options at checkout (e.g. by bank, basket amount).

**Where it is used**

* **omnishop.payments.strategies** — In `_filter_installments`: `for installment_filter in dynamic_settings.INSTALLMENT_FILTERS`; each filter is instantiated with `import_string(installment_filter["klass"])(**installment_filter["kwargs"])` and its `filter(basket, installments, **kwargs)` is called to get filtered installments and optional messages.

Key: `INSTALLMENT_FILTERS`\
Type: array\
Default: `()` (from Django `INSTALLMENT_FILTERS`)

**Example**

`[{"klass": "omnishop.payments.filters.SomeFilter", "kwargs": {"min_amount": 100}}]`

### **78. LoyaltyMoneyEnabled**

When `true`, loyalty money (balance from loyalty account) can be used as a payment method; basket exposes `loyalty_amount` and payment strategies may allow paying with loyalty. When `false`, loyalty amount is zero and loyalty payment is disabled.

**Where it is used**

* **omnishop.orders.models.Order** — `loyalty_amount` cached property: `if not dynamic_settings.LOYALTY_MONEY_ENABLED: return 0`; otherwise computes available loyalty balance for the order currency.
* **omnishop.payments.strategies** — `pay_loyalty_money()` returns `False` when `not dynamic_settings.LOYALTY_MONEY_ENABLED`; controls whether loyalty can be applied as payment.

Key: `LOYALTY_MONEY_ENABLED`\
Type: boolean\
Default: false

**Example**

Set to `true` when you offer loyalty points/cash as a payment option at checkout.

### **79. SwapPrettyUrlsForNotListableProducts**

When `true`, pretty URL generation for products uses different logic when the canonical product is not listable: e.g. `incr_new_path` in `ProductUrlGenerator` considers existing URLs with a numeric suffix pattern and can swap to a listable variant. When `false`, the default (super) behaviour is used and non-listable products may not get the same URL handling.

**Where it is used**

* **omnishop.prettyurls.generators.ProductUrlGenerator** — In `incr_new_path`: `if not getattr(dynamic_settings, 'SWAP_PRETTY_URLS_FOR_NOT_LISTABLE_PRODUCTS', False): return super().incr_new_path(...)`; when true, uses custom logic (regex, annotate index, existing\_urls) to handle new paths for non-listable products.

Key: `SWAP_PRETTY_URLS_FOR_NOT_LISTABLE_PRODUCTS`\
Type: boolean\
Default: false

**Example**

Set to `true` when you want pretty URLs to resolve to a listable variant when the primary product is not listable.

### **80. ImageOptimizationLimit**

Quality limit (e.g. 0–100) used when adjusting image quality during upload or processing. Passed to the image adjustment function so uploaded images are compressed or resized to meet the limit.

**Where it is used**

* **omnicore\_project.omnicore.products.enums** — When saving an image-type attribute: `quality_limit = get_setting("IMAGE_OPTIMIZATION_LIMIT", default=90)`; value is passed to `adjust_image_quality(..., limit=quality_limit)` before saving to storage.
* **omnicore\_project.omnicore.eav.models** — Same pattern for image attribute handling.

Key: `IMAGE_OPTIMIZATION_LIMIT`\
Type: int\
Default: 90

**Example**

Lower (e.g. 70) for smaller file sizes; higher (e.g. 95) for better quality and larger files.

### **81. ListableProductRules**

List of dotted paths to rule callables that filter or sort variant products to pick the listable one for a group/meta product. Rules are applied in order (e.g. image\_rule → keep variants with image; is\_listable\_rule → keep listable; min\_price\_rule → sort by price ascending).

**Where it is used**

* **omnishop.products.listable\_rules.get\_listable\_product\_rules** — `rules = dynamic_settings.LISTABLE_PRODUCT_RULES`; returns `[import_string(r) for r in rules]`.
* **omnishop.products.service** — When resolving the listable product: `for rule in get_listable_product_rules(): filtered_variants = rule(filtered_variants)`; the final variant(s) determine the listable product.

Key: `LISTABLE_PRODUCT_RULES`\
Type: list (dotted paths)\
Default: `["omnishop.products.listable_rules.image_rule", "omnishop.products.listable_rules.is_listable_rule"]`

**Example**

Add `omnishop.products.listable_rules.min_price_rule` or `max_price_rule` to prefer cheapest or most expensive variant; add `stock_rule` to prefer in-stock variants.

### **82. IsMarketplace**

When `true`, the shop operates as a marketplace (e.g. multiple sellers or data sources). Basket and product logic may enforce data-source or seller checks. When `false`, single-seller behaviour.

**Where it is used**

* **omnishop.baskets.resource.serializers** — In `validate()`: `is_marketplace = dynamic_settings.IS_MARKETPLACE`; when true, `_validate_datasource(attrs)` is called so basket items are validated against allowed data sources.

Key: `IS_MARKETPLACE`\
Type: boolean\
Default: false

**Example**

Set to `true` when you have multiple sellers or data sources and need datasource validation on add-to-basket.

### **83. AttributesOrderReplace**

Map of attribute key → another attribute key. When building product attribute display/order data, if the product has both the source key and the target key, the **order** of the source attribute is taken from the target attribute's order (used to reorder or align attribute display).

**Where it is used**

* **omnishop.products.models** — When building attribute data (e.g. for filters/display): `reorder_attr = getattr(dynamic_settings, 'ATTRIBUTES_ORDER_REPLACE', {})`; if `reorder_attr.get(attribute_key)` exists and that target is in `product.attributes`, the order value from the target attribute is used for the current attribute's order.

Key: `ATTRIBUTES_ORDER_REPLACE`\
Type: object\
Default: `{}`

**Example**

`{"color": "size"}` to use the order of the "size" attribute for the "color" attribute when both exist.

### **84. HideGroupedProductsOnSearch**

When `true`, grouped products are hidden from search by blanking their (and their sub-products') name and search\_text in the search index so they do not match. When `false`, grouped products can appear in search results.

**Where it is used**

* **omnishop.search.indexer** — When indexing a grouped product: `if instance.product_type == ProductTypes.grouped and dynamic_settings.HIDE_GROUPED_PRODUCTS_ON_SEARCH:` then for each sub\_product `sub_product_.name, sub_product_.search_text = '', ''` so they are not findable in search.

Key: `HIDE_GROUPED_PRODUCTS_ON_SEARCH`\
Type: boolean\
Default: false

**Example**

Set to `true` to only show simple/variant products in search and hide group parents.

### **85. CategoryProductExtractionStrategy**

Dotted path to the strategy class that extracts products for category pages (which products to return from search/DB for a category listing).

**Where it is used**

* **omnishop.search.strategies.get\_category\_product\_extraction\_strategy** — Returns `import_string(getattr(dynamic_settings, 'CATEGORY_PRODUCT_EXTRACTION_STRATEGY', '...'))`; that strategy is used when building category product lists.

Key: `CATEGORY_PRODUCT_EXTRACTION_STRATEGY`\
Type: string (dotted path)\
Default: `omnishop.search.strategies.DefaultProductExtractionStrategy`

**Example**

Use `MinPriceFirstProductExtractionStrategy` or `ListableFirstProductExtractionStrategy` etc. to change which product is shown first for a category.

### **85.1. Category Pre Filter**

Pre-filter applied to category querysets in search. The value is a dictionary of Django ORM lookup arguments (e.g. `{"is_active": True}`). It is passed as `**kwargs` to `.filter()` on the categories queryset before further use.

**Where it is used**

* **omnishop.search.utils.pre\_filter\_categories** — `pre_filter = getattr(dynamic_settings, 'CATEGORY_PRE_FILTER', {})`; if set, returns `categories.filter(**pre_filter)`; otherwise returns categories unchanged. Called from search services and widgets when resolving categories for search or category options (e.g. CategoryWidget).

Key: `CATEGORY_PRE_FILTER`\
Type: object (dict)\
Default: `{}`

**Example**

Set to `{"is_active": True}` to only include active categories in search and widgets.

### **86. SearchProductExtractionStrategy**

Dotted path to the strategy class that extracts products for search result pages (which products to show per search hit, e.g. default, first item, listable first, min/max price first, all sub-products).

**Where it is used**

* **omnishop.search.strategies.get\_search\_product\_extraction\_strategy** — Reads `getattr(dynamic_settings, "SEARCH_PRODUCT_EXTRACTION_STRATEGY", "...")` and returns `import_string(extraction_strategy)`.
* **omnishop.search.resources.views** — Search view uses `extraction_strategy or get_search_product_extraction_strategy(seller_ids)` when building search results.

Key: `SEARCH_PRODUCT_EXTRACTION_STRATEGY`\
Type: string (dotted path)\
Default: `omnishop.search.strategies.DefaultProductExtractionStrategy`

**Example**

Use `FirstItemSearchExtractionStrategy`, `MinPriceFirstProductExtractionStrategy`, or `AllSubProductsExtractionStrategy` etc. to change how search results are built.

### **87. EnableExternalCostCalculator**

When `true`, shipping cost is calculated via the external cost calculator API (`EXTERNAL_COST_CALCULATOR`). When `false`, the Omnitron API client is used for shipping cost.

**Where it is used**

* **omnishop.shippings.calculators** — The calculator's `client` classproperty: `is_calculator_external = getattr(dynamic_settings, 'USE_EXTERNAL_COST_CALCULATOR', False)`; when true, `cls._client = ExternalCostCalculatorApiClient()`; otherwise `OmnitronApiClient()`. That client is used for `calculate_shipping_cost`.

Key: `USE_EXTERNAL_COST_CALCULATOR`\
Type: boolean\
Default: false

**Example**

Set to `true` and configure `EXTERNAL_COST_CALCULATOR` when you use an external shipping cost API instead of Omnitron.

### **88. ExternalCostCalculatorAPI**

Configuration for the external cost calculator API (validated by `ExternalCostCalculatorAPISerializer`). Typically includes `host` (URL) and `token` (auth). Used when `USE_EXTERNAL_COST_CALCULATOR` is true.

**Where it is used**

* **omnishop.shippings.client.ExternalCostCalculatorApiClient** — In `calculate_shipping_cost`: `external_cost_calculator_url = getattr(dynamic_settings, 'EXTERNAL_COST_CALCULATOR', {}).get("host")`, `self.access_token = ...get("token")`; the client POSTs to that URL with the token to get shipping cost.

Key: `EXTERNAL_COST_CALCULATOR`\
Type: object\
Default: `{}`

**Example**

`{"host": "https://api.example.com/calculate-shipping/", "token": "..."}`

### **89. ProductAlertQuantity**

Minimum stock quantity for a product to be considered "in stock" for product alerts. When stock is >= this value, "back in stock" notifications can be sent to users who signed up for alerts.

**Where it is used**

* **omnishop.wishlists.tasks** — In the product alerts task: `product__productstock__stock__gte=dynamic_settings.PRODUCT_ALERT_QUANTITY` filters product alerts so only products with stock at or above this threshold are processed for sending alert emails.

Key: `PRODUCT_ALERT_QUANTITY`\
Type: int\
Default: 1

**Example**

Set to a higher value (e.g. 5) if you only want to notify when there is meaningful stock; keep at 1 for "any stock" alerts.

### **90. ApplePayDomainAssociation**

Content of the Apple Pay domain verification file (merchant domain association). Served at the well-known URL so Apple can verify the domain for Apple Pay.

**Where it is used**

* **omnishop.payments.views.WellKnowView** — For slug `apple-developer-merchantid-domain-association.txt` or `apple-developer-merchantid-domain-association`: `content = getattr(dj_dynamic_settings, 'APPLE_PAY_DOMAIN_ASSOCIATION', '')`; returned as HttpResponse with `text/plain`. Used for Apple Pay domain verification.

Key: `APPLE_PAY_DOMAIN_ASSOCIATION`\
Type: string\
Default: ““

### **91. OfferMiscellaneousAttributeKey**

Product attribute key (e.g. `cannot_be_sold_alone`) that indicates whether an offer product can be sold alone. When this attribute is true on an offer, the product must be bought with another product (e.g. sub\_items or main product); basket and order logic enforce this.

**Where it is used**

* **omnishop.baskets.resource.serializers** — In add/set basket item validation: `offer_miscellaneous_key = dynamic_settings.OFFER_MISCELLANEOUS_ATTRIBUTE_KEY`; used to check `sub_item['product'].attributes.get(offer_miscellaneous_key, False)` and reject unexpected sub items when the offer cannot be sold alone.
* **omnishop.baskets.service** — `offer_miscellaneous_attr_key = dynamic_settings.OFFER_MISCELLANEOUS_ATTRIBUTE_KEY`; used when adding to basket to limit quantity or enforce form/extra product rules for offer/miscellaneous products.
* **omnishop.orders.service** — Same key used for order-level offer/miscellaneous checks.

Key: `OFFER_MISCELLANEOUS_ATTRIBUTE_KEY`\
Type: string\
Default: `cannot_be_sold_alone`

**Example**

Use the attribute key that your product data uses to mark "cannot be sold alone" offers; default is `cannot_be_sold_alone`.

### **92. BasketNamespaceValidator**

This configuration allows you to manage the validations applied within the multi-basket feature.

Key: `BASKET_NAMESPACE_VALIDATORS`\
Type: list\
Default:

```
[ 
   {
       "validator_klass":    "omnishop.baskets.validator.NullNamespaceValidator",
       "kwargs": {}
   },
   {
       "validator_klass": "omnishop.baskets.validator.ActiveBasketCountValidator",
       "kwargs": {
           "limit": 5
       }
   }
]
```

#### **Possible Values**

**NullNamespaceValidator**

Regardless of whether the multibasket feature is used or not, the namespace field cannot be null when adding products to the basket. The field should either be left out entirely or filled with a valid value.

* **For brands that do not want to use the multibasket feature,** the `NullNamespaceValidator` (which is set as the default) should be used. This validator prevents any information from being sent in the namespace field.
* **For brands that want to use the multibasket feature,** the `NullNamespaceValidator` should not be used.

**Key:**

```
{
    "validator_klass": "omnishop.baskets.validator.NullNamespaceValidator",
    "kwargs": {}
}
```

**NamespaceDataSourceValidator**

Restricts the namespaces a user can enter to the slugs associated with the data source. Any additional namespaces that should be permitted can be added through `allowed_custom_namespaces`.\
**Key:**

```
{
    "validator_klass": "omnishop.baskets.validator.NamespaceDataSourceValidator",
    "kwargs": {
        "allowed_custom_namespaces": ["custom-namespace"]
    }
}
```

**ActiveBasketCountValidator**\
This feature limits the number of baskets a user can create. By default, the limit is set to 5, but it can be adjusted by adding a validator, as shown below.\
**Key:**

```
{
    "validator_klass": "omnishop.baskets.validator.ActiveBasketCountValidator",
    "kwargs": {
        "limit": 2
    }
}
```

### **93. CheckoutShippingOptionSelectionPage**

This configuration determines which page will be used for displaying shipping options.

Key: `CHECKOUT_SHIPPING_OPTION_SELECTION_PAGE`\
Type: string\
Default: ShippingOptionSelectionPage

#### **Possible Values**

**ShippingOptionSelectionPage**

* The standard shipping option page.

**DataSourceShippingOptionSelectionPage**

* This page is selected to list shipping options specific to the seller.

**AttributeBasedShippingOptionSelectionPage**

* This page is selected to group shipping options based on the attributes specified in the product.

### **94. AttributeKeyForAttributeBasedShippingOption**

This configuration is required to group shipping options specific to a product. The shipping option details are stored in the product attributes, and this setting defines the key where that information is kept.

**Note**: The `CHECKOUT_SHIPPING_OPTION_SELECTION_PAGE` configuration must be set to `AttributeBasedShippingOptionSelectionPage`.

Key: `ATTRIBUTE_KEY_FOR_ATTRIBUTE_BASED_SHIPPING_OPTION`\
Type: string\
Default: null

### **94.1. RemoteShippingOptionProvider**

Configuration for the remote shipping option provider used to fetch available shipping options from an external service (e.g. extension). The provider is instantiated with `klass` and `conf`; it must implement `get_shipping_option_slugs(pre_order)` and return a list of shipping option slugs. When using this provider, set `CHECKOUT_SHIPPING_OPTION_SELECTION_PAGE` to `REMOTE_SHIPPING_OPTION_SELECTION_PAGE` so checkout uses the remote flow.

**Where it is used**

* **omnishop.shippings.service.RemoteShippingOptionService** — In `__init__`: `remote_shipping_option_provider = dynamic_settings.REMOTE_SHIPPING_OPTION_PROVIDER`; `klass = import_string(remote_shipping_option_provider["klass"])`, `self.provider = klass(**conf)`. In `get_remote_shipping_options(pre_order)`: calls `self.provider.get_shipping_option_slugs(pre_order)` then resolves `ShippingOption` objects by slug and applies rules/calculators. Exceptions from the remote provider (timeout, provider error, validation) use `omnishop.shippings.remote_shipping_options.exceptions` (e.g. `RemoteShippingOptionTimeoutException`, `RemoteShippingOptionProviderException`).

Key: `REMOTE_SHIPPING_OPTION_PROVIDER`\
Type: object (`klass` + `conf`)\
Default: `{"klass": "omnishop.shippings.remote_shipping_options.providers.ExtensionShippingOptionProvider", "conf": {"base_url": "", "username": "", "password": ""}}`

**Example**

```json
{
    "klass": "omnishop.shippings.remote_shipping_options.providers.ExtensionShippingOptionProvider",
    "conf": {
        "base_url": "https://extension.example.com",
        "username": "api_user",
        "password": "api_pass"
    }
}
```

Set `CHECKOUT_SHIPPING_OPTION_SELECTION_PAGE` to `REMOTE_SHIPPING_OPTION_SELECTION_PAGE` when using this provider.

### **95. BasketValidators**

Basket validators are used to validate basket items before allowing them to proceed to checkout.

Key: `BASKET_VALIDATORS`\
Type: list\
Default: \[]

#### **Possible Values**

**BasketItemQuantityValidator**

* Validates the quantity limits for products that match the specified `attribute_name` and `attribute_value` pairs.

```
[
    {
        "condition_klass": "omnishop.baskets.validator.BasketItemQuantityValidator",
        "kwargs": {
            "attribute_name": "limited_product",
            "attribute_value": true,
            "upper_limit": 5,
            "lower_limit": 1
        },
        "message": {
            "tr-tr": "{lower_limit} ile {upper_limit} arasinda olmalidir"
        }
    }
]
```

**BasketItemBaseCodeQuantityValidator**

* Similar to `BasketItemQuantityValidator` but groups products by base code for quantity control.

```
[
    {
        "condition_klass": "omnishop.baskets.validator.BasketItemBaseCodeQuantityValidator",
        "kwargs": {
            "attribute_name": "limited_product",
            "attribute_value": true,
            "upper_limit": 5,
            "lower_limit": 1
        },
        "message": {
            "tr-tr": "Base code {} quantitiy exceeded"
        }
    }
]
```

**BasketItemSteppedQuantityValidator**

* Ensures that the quantity of a specific product added to the basket is in multiples of the step value specified in its attributes.

```
[
    {
        "condition_klass": "omnishop.baskets.validator.BasketItemSteppedQuantityValidator",
        "kwargs": {
            "attribute_name": "integration_urun_artis_miktar",
            "upper_limit_attribute_name": "integration_max_quantity",
            "lower_limit_attribute_name": "integration_min_quantity",
        },
        "message": {
            "tr-tr": "{step}'nin katlari ve {lower_limit} ile {upper_limit} arasinda olmalidir",
        }
    }
]
```

**AttributeValidator**

* Validates whether products that match specified `attribute_name` and `expected_value` pairs can be added to the basket. If the `attribute_name` is absent in the product's attributes, validation is bypassed.

```
[
    {
        "condition_klass": "omnishop.baskets.validator.AttributeValidator",
        "kwargs": {
            "attribute_name": "cannot_be_sold_alone",
            "expected_value": false,
            "disabled_on_sub_basket_items": false
        },
        "message": {}
    }
]
```

**SingleDataSourceValidator**

* Validates that only one seller's products are present in the basket.

```
[
    {
        "condition_klass": "omnishop.baskets.validator.SingleDataSourceValidator",
        "kwargs": {},
        "message": {}
    }
]
```

### **96. BasketServiceConditions**

A list of conditions that are checked when an item is added to the basket.

Key: `BASKET_SERVICE_CONDITIONS`\
Type: list\
Default: \[]

#### **Possible Values**

**omnishop.baskets.conditions.basket\_tradable\_item\_is\_single**

* Verifies that only items that can be added alongside a tradable product are allowed in the basket.

**omnishop.baskets.conditions.basket\_user\_max\_quantity**

* Enforces the maximum quantity of a product that a user can purchase. This should be used with the `BASKET_PRODUCT_USER_MAX_QUANTITY_ATTRIBUTE` setting. It counts the user's orders within the time window (in hours) defined by `BASKET_PRODUCT_USER_MAX_QUANTITY_GAP_HOUR` (default: 24 hours).

**omnishop.baskets.conditions.basket\_item\_max\_count**

* Limits the maximum number of products in the basket. This should be used with the `BASKET_MAX_ITEM_COUNT` setting.

**omnishop.baskets.conditions.bundle\_type\_compatibility\_condition**

* ​​Ensures that certain products, which are not allowed to be in the same basket, are checked for compatibility. This condition should be used in conjunction with the `BUNDLE_TYPE_CONFIGURATION` setting.

**omnishop.baskets.conditions.extra\_product\_quantity\_condition**

* Prevents extra products from being added to the basket as parent items.

### **97. AttributeKeyForMergeableOrderItem**

Defines the attribute that stores information about whether a product is mergeable.

Key: `ATTRIBUTE_KEY_FOR_MERGEABLE_ORDER_ITEM`\
Type: string\
Default: allow\_mergeable\_order\_item

### **98. SendTradeinMailCase**

Controls whether an email is sent regarding the status of trade-in orders.

Key: `SEND_TRADEIN_MAIL_CASE`\
Type: boolean\
Default: false

### **99. AllowTradeinWithOtherProducts**

Determines whether other products can be added to the basket alongside trade-in products.

Key: `ALLOW_TRADEIN_WITH_OTHER_PRODUCTS`\
Type: boolean\
Default: false

### **100. UseOneTimeToken**

Allows the user to log in again using a one-time token. The `OneTimeTokenMiddleware` middleware must also be included.

Key: `USE_ONE_TIME_TOKEN`\
Type: boolean\
Default: true

### **101. BasketProductUserMaxQuantityAttribute**

Defines the attribute that stores information about the maximum quantity of a product that a user can purchase. This should be used in conjunction with the `basket_user_max_quantity` condition.

Key: `BASKET_PRODUCT_USER_MAX_QUANTITY_ATTRIBUTE`\
Type: string\
Default: `""`

**Example**

Set to the exact content of the file Apple provides for your domain (or leave empty if not using Apple Pay).

### **101.1. BasketProductUserMaxQuantityGapHour**

Defines the time window (in hours) used when enforcing the per-user maximum quantity for a product. The `basket_user_max_quantity` condition counts how many of the product the user has ordered within this many hours and blocks adding to basket if the total would exceed the limit from `BASKET_PRODUCT_USER_MAX_QUANTITY_ATTRIBUTE`.

**Usage:** Used by the basket service condition `omnishop.baskets.conditions.basket_user_max_quantity`. Order items with `created_date` within the last `BASKET_PRODUCT_USER_MAX_QUANTITY_GAP_HOUR` hours (excluding cancelled/refunded) are summed; the user cannot add more of that product to the basket than the attribute allows minus that sum.

Key: `BASKET_PRODUCT_USER_MAX_QUANTITY_GAP_HOUR`\
Type: number (int)\
Default: 24

### **102. BasketProductMaxQuantityAttribute**

Defines the attribute that stores information about the maximum quantity of a product that can be added to the basket.

Key: `BASKET_PRODUCT_MAX_QUANTITY_ATTRIBUTE`\
Type: string\
Default: “”

### **103. TradeinConf**

Stores the trade-in configurations, including `tradein_product_sku_attribute`, `tradein_product_identifier_field`, and `client` configurations.

Key: `TRADEIN_CONF`\
Type: object\
Default: {}

### **104. CancellationRequestAddressRequired**

Makes address information mandatory when creating a cancellation request for a refund.

Key: `CANCELLATION_REQUEST_ADDRESS_REQUIRED`\
Type: boolean\
Default: false

### **105. IsSubBasketItemQuantitySameAsParent**

Ensures that the quantity of the child item matches the quantity of the parent item when added to the basket.

Key: `IS_SUB_BASKET_ITEM_QUANTITY_SAME_AS_PARENT`\
Type: boolean\
Default: false

### **106. GuestUserRegistrationRequiresEmailVerification**

Requires guest users to verify their email in order to log in after placing an order.

Key: `GUEST_USER_REGISTRATION_REQUIRES_EMAIL_VERIFICATION`\
Type: boolean\
Default: false

### **107. BukalemunConfig**

Stores the application credentials required to connect to the Bukalemun application.

Key: `BUKALEMUN_CONFIG`\
Type: object\
Default: {}

### **108. PickupLocationProvider**

Stores the credentials of the relevant application for pickup locations.

Key: `PICKUP_LOCATION_PROVIDER`\
Type: object\
Default:

```
{
    "klass": "omnishop.address.pickup_locations.provider.PickupLocationProvider",
    "conf": {
        "base_url": "",
        "username": "",
        "password": ""
    }
}
```

### **109. RemotePriceAttributeKey**

Defines the attribute that, if present in a product's attributes, allows the product to be re-priced during the checkout step via the client.

Key: `REMOTE_PRICE_ATTRIBUTE_KEY`\
Type: string\
Default: has\_remote\_price

### **110. BasketItemRemotePriceClientConf**

Stores the client credentials used to retrieve the final prices of items in the basket during the checkout step.

Key: `BASKET_ITEM_REMOTE_PRICE_CLIENT_CONF`\
Type: object\
Default:

```
{
    "headers": {
        "Authorization": "Bearer test_token"
    },
    "url": "http://localhost:8000/"
}
```

### **111. ResetBasketOnSegmentChange**

Controls the reset behavior of the basket when the segment changes. If enabled, the basket will reset whenever a new segment is selected.

Key: `RESET_BASKET_ON_SEGMENT_CHANGE`\
Type: boolean\
Default: true

### **112. AkifastUsePhone**

Determines whether the Akifast phone number will be used when creating a user upon logging in with Akifast.

Key: `AKIFAST_USE_PHONE`\
Type: boolean\
Default: true

### **113. ShowProductPriceExtraField**

Controls whether additional information related to the product price will be displayed.

Key: `SHOW_PRODUCT_PRICE_EXTRA_FIELD`\
Type: boolean\
Default: false

### **114. XForwardedForRules**

Defines the rules for handling the 'X-Forwarded-For' header IP addresses.

For example, `{1:2, 2:3}` means the first IP address will be replaced with the second, and the second with the third.

These rules help the server accurately determine the actual client IP address.

Set `CLIENT_IP_RESOLVER_FUNCTION` to `omnicore.utils.get_secure_client_ip` for correct client IP resolution.

Key: `X_FORWARDED_FOR_RULES`\
Type: object\
Default: {}

### **115. DeliveryBagsConf**

Set users manually add bag products to their basket, with full control over the quantity. For example, after adding 5 kg of tomatoes and 3 kg of potatoes to the basket, users can manually include a bag product and adjust its quantity.

Bag products cannot be purchased independently and must be added alongside other products.

Key: `DELIVERY_BAGS_CONF`

* **Sku:** Must contain the SKU information of the Miscellaneous-type delivery bags product where is\_active=True.
* **Is\_active:** Reflects whether the Delivery Bags feature is active or not.
* **Per\_amount:** Defines how many basket amounts the delivery bag can accommodate.
* **Auto\_run:** Specifies whether automatic delivery bags will be used from the Slot page.
* **Min\_quantity:** Indicates the minimum quantity value for the delivery bag. The default value is set to 0.
* **Is\_quantity\_changeable:** Determines if the quantity can be updated by the user on the Delivery Bags page. Accepts a True/False value, with the default set to False.
* **Exclude\_bag\_attribute\_name:** Ensures that products with this tag are excluded from the basket amount calculation. The default value is 'exclude\_bag\_attribute\_name'.

!\[]\[image3]

### **116. Attribute Keys For Attribute Based Shipping Option**

This dynamic setting allows you to specify the rule for attribute keys that will be used for grouping shipping options.

For detailed information about this dynamic setting, please refer to the [Attribute Based Shipping Options](https://developers.akinon.com/docs/guide/commerce/attribute-based-shipping-options/#key-dynamic-settings) document.

Key: `ATTRIBUTE_KEYS_FOR_ATTRIBUTE_BASED_SHIPPING_OPTION`\
Type: Object\
Default: \[ ]

### **117. Auto Use Coupons**

If a coupon applicable to the user's basket exists, the system automatically applies it. The default value is set to `True`.

**Effect:**

* **True:** Coupons are automatically applied.
* **False:** Users must manually select coupons.

Key: `AUTO_USE_COUPONS`\
Type: Boolean\
Default: True

### **118. Basket Item Attributes Max Length KB**

Limits the total size of the attributes field on basket items in kilobytes (KB). The default value is `0`.

**Effect:**

* If a value is specified, the total size of attributes on basket items cannot exceed the specified KB limit.
* A value of `0` allows unlimited size.

Key: `BASKET_ITEM_ATTRIBUTES_MAX_LENGTH_KB`\
Type: Integer\
Default: 0

### **119. Can Guest Purchase**

Determines whether guest users (users who have not created an account) can make purchases. The default value is set to `True`.

**Effect:**

* **True:** Guests can purchase without creating an account.
* **False:** Users must log in or create an account to make a purchase.

This setting is enabled by default to allow guest users to shop quickly and efficiently. If additional security or user information verification is required, you can set this option to `False`.

Key: `CAN_GUEST_PURCHASE`\
Type: Boolean\
Default: True

### **120. Cancellation Request Max Image Count Per Order Item**

Specifies the maximum number of images that can be uploaded per order item during a cancellation request. The default value is `3`.

**Effect:**

* Allows up to the specified number of images.
* A value of `0` disallows image uploads.

This setting can be used to enable users to provide more detailed information with images in their cancellation requests while managing the storage load on the system.

Key: `CANCELLATION_REQUEST_MAX_IMAGE_COUNT_PER_ORDER_ITEM`\
Type: Integer\
Default: 3

### **121. CRM Gateway**

Contains the configuration required for CRM (Customer Relationship Management) integration. Defaults to an empty object `{}`.

**Format:**

This setting is an object format consisting of `{klass, block_guest_for_receiver, conf}`.

| Key                        | Type    | Description                                                                                                                                                                        |
| -------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `klass`                    | String  | Specifies the class performing CRM operations.                                                                                                                                     |
| `conf`                     | Obje    | Contains the configuration details for the CRM class.                                                                                                                              |
| `block_guest_for_receiver` | Boolean | Determines if guest users are included in CRM synchronization. If set to `False`, the `CRM_SYNCHRONIZATION_ACTIVE` setting must be set to `True` for synchronization with the CRM. |

Key: `CRM_GATEWAY`\
Type: Object\
Default: { }

### **122. CRM Synchronization Active**

Defines whether CRM synchronization is active during user creation or update operations. This setting controls whether user data is synchronized with the CRM system. By default, it is set to `False`.

**Effect:**

* **True:** CRM synchronization is enabled, and user creation or update operations are synchronized with the CRM system.
* **False:** CRM synchronization is disabled, and user data is not synchronized with the CRM system.

**Note:**

* If this setting is enabled (`True`), CRM synchronization becomes active, and additional processing occurs during every user creation or update operation. This may introduce a performance load on the system, potentially causing delays in database access during periods of high traffic.
* When `CRM_SYNCHRONIZATION_ACTIVE` is set to `True`, the `block_guest_for_receiver` property in the `CRM_GATEWAY` becomes effective.
  * If the user is a guest and `block_guest_for_receiver` is active, synchronization for guest users will be blocked.
  * If `block_guest_for_receiver` is inactive, guest users can be synchronized.

Key: `CRM_SYNCHRONIZATION_ACTIVE`\
Type: Boolean\
Default: False

### **123. DB Templates Prefixes**

Defines the list of valid prefixes for database templates. This setting is used to specify which templates can be stored in the database. The default value is set to `["emails", "sms"]`.

This setting ensures that templates are stored in the correct categories and remain organized. If additional types of templates are required in the system, the relevant prefixes can be added to this list.

**Note:**

* Removing the prefixes `"emails"` and `"sms"` may cause existing templates to malfunction. Therefore, it is recommended to avoid removing these prefixes.

Key: `DB_TEMPLATES_PREFIXES`\
Type: List\[string]\
Default: \["emails", "sms"]

### **124. Deferred Slot Ignored Days**

Specifies which days should be excluded when calculating deferred slot days for appointment-based orders. This configuration allows certain days to be omitted from the calculations. By default, this setting is an empty list.

**Valid Values:**

`[0, 1, 2, 3, 4, 5, 6]`

* `0`: Monday
* `1`: Tuesday
* `2`: Wednesday
* `3`: Thursday
* `4`: Friday
* `5`: Saturday
* `6`: Sunday

**Effect:**

* To exclude a specific day from the calculation of the slot start date, add its numeric value to the list.
* For example, if weekends should be excluded, set `DEFERRED_SLOT_IGNORED_DAYS = [5, 6]`. In this case, Saturday and Sunday will not be considered in the calculations.

Key: `DEFERRED_SLOT_IGNORED_DAYS`\
Type: List\[choices=(0, 1, 2, 3, 4, 5, 6)]\
Default: \[ ]

### **125. Ignore Accept Language Header**

Determines whether the `Accept-Language` header in requests should be ignored. This setting controls whether language preferences are configured based on the header information. By default, it is set to `True`.

**Impact:**

* **True**: The `Accept-Language` header is ignored.
* **False**: The `Accept-Language` header is respected, and language preferences are set based on this header.

**Notes:**

* To use the language preference from the `Accept-Language` header, set `IGNORE_ACCEPT_LANGUAGE_HEADER` to `False`.
* When this setting is `False`, changing the language via the `/i18n/setlang/` endpoint is disabled, and language preference can only be configured through the header.

Key: `IGNORE_ACCEPT_LANGUAGE_HEADER`\
Type: Boolean\
Default: True

### **126. Max Image Dimensions**

This setting is applicable only for cancellation request operations, and it defines the maximum width and height (in pixels) for images. It ensures that the specified dimensions are not exceeded.

**Format:**\
This setting is an object format consisting of a `{width, height}` pair.

* `width`: Maximum width (in pixels).
* `height`: Maximum height (in pixels).

**Note:**

* By controlling image dimensions, you can prevent the system from being overloaded with excessively large images. This setting requires integration with Omnitron, and it is essential for compatibility across both systems.

Key: `MAX_IMAGE_DIMENSIONS`\
Type: Object {“width”, “height”}\
Default:

```py
{
    "width": 2000,
    "height": 2000,
}
```

### **127. Menu Renderer Config**

The `MENU_RENDERER_CONFIG` is a configuration object used to define the structure and behavior of menu rendering in OmniShop. This configuration is represented as an object containing the `generators` and `modifiers` pairs.

**Generator**: Responsible for creating the initial set of menu items. Below is the available generator:

* omnishop.menus.generators.MenuItemModelGenerator

**Modifiers**: Adjust and refine the menu structure and attributes. Below are the available modifiers and their descriptions:

| Path                                             | Description                                                                                                                                                       |
| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| omnishop.menus.modifiers.SortByLevelMenuModifier | Sorts menu items based on their hierarchy level and predefined sorting order. Ensures the items are displayed in the correct hierarchical sequence.               |
| omnishop.menus.modifiers.SetParentMenuModifier   | Assigns the `parent` property to each menu item. This is achieved by using the `parent_pk` attribute to locate and set the appropriate parent item from the list. |
| omnishop.menus.modifiers.SortingMenuModifier     | Ensures each item is positioned correctly based on its `level` and `sort_order`.                                                                                  |
| omnishop.menus.modifiers.SetSelectedMenuModifier | Identifies and marks the currently selected menu item within the menu tree.                                                                                       |

Key: `MENU_RENDERER_CONFIG`\
Type: Object\
Default: It is recommended to use default configuration for optimal results.

```py
{
    'generators': (
        'omnishop.menus.generators.MenuItemModelGenerator',
    ),
    'modifiers': (
        'omnishop.menus.modifiers.SortByLevelMenuModifier',
        'omnishop.menus.modifiers.SetParentMenuModifier',
        'omnishop.menus.modifiers.SortingMenuModifier',
        'omnishop.menus.modifiers.SetSelectedMenuModifier',
    ),
}
```

### **128. Read Currency From Header**

This setting controls how the currency is determined.

**Effect:**

* **True**: The currency is retrieved from the `x-currency` header. If the `x-currency` value is not found in the `AVAILABLE_CURRENCIES` dynamic setting, the `DEFAULT_CURRENCY` dynamic setting is used.
* **False**: The currency is determined using the `/users/activate-currency/` endpoint. If it is not defined, the `DEFAULT_CURRENCY` dynamic setting is used.

**Note:**

* When this setting is enabled, the `/users/activate-currency/` endpoint becomes inactive.

Key: `READ_CURRENCY_FROM_HEADER`\
Type: Boolean\
Default: False

### **129. Send Invoice Available Notifications**

This setting determines whether notifications are sent to users when an invoice is available. If set to `True`, a notification informing the user that the invoice is available will be sent. If set to `False`, no such notifications will be sent.

Key: `SEND_INVOICE_AVAILABLE_NOTIFICATIONS`\
Type: Boolean\
Default: True

### **130. Throttling Settings**

This setting controls the number of requests. It is used for traffic management and maintaining system stability. By defining different limits for successful and total requests according to needs, a more flexible throttling mechanism can be created.

Scope and default values for controlling **total requests:**

| Scope                              | Count | Duration |
| ---------------------------------- | :---: | :------: |
| `register`                         |   5   |   1800   |
| `login`                            |   5   |   1800   |
| `register-with-loyalty`            |   5   |   1800   |
| `password-reset`                   |   3   |    60    |
| `send-message`                     |   10  |    60    |
| `user-unsubscribe`                 |   10  |   1800   |
| `create-order-request`             |   10  |   1800   |
| `retail-store-stock`               |   10  |   1800   |
| `cancellation-request-bulk-create` |   1   |     5    |
| `basket`                           |   10  |    60    |
| `create-address`                   |   5   |    60    |
| `user-permissions`                 |   10  |    60    |
| `appointment-date`                 |   10  |    60    |
| `user-segment`                     |   3   |    60    |

Scope and default values for controlling **successful requests:**

| Scope                 | Success | Duration |
| --------------------- | :-----: | :------: |
| `successful-register` |    3    |   1800   |
| `profile-update`      |    5    |   1800   |

Basarili istekleri kontrol eden scope ve varsayılan değerleri:

**Format:**

```py
{
  "scope_key": {    "count": 1, # Quantity
    "duration": 2, # Seconds  }
}
```

**Note:** If the scope is for successful requests, `success` should be used instead of `count`.

Key: `THROTTLING_SETTINGS`\
Type: Object\
Default:

```py
{
    'register': {
        'count': 5,
        'duration': 30 * 60
    },
    'successful-register': {
        'success': 3,
        'duration': 30 * 60
    },
    'login': {
        'count': 5,
        'duration': 30 * 60,
    },
    'register-with-loyalty': {
        'count': 5,
        'duration': 30 * 60,
    },
    'profile-update': {
        'success': 5,
        'duration': 30 * 60
    },
    'password-reset': {
        'count': 3,
        'duration': 60
    },
    'send-message': {
        'count': 10,
        'duration': 60
    },
    'user-unsubscribe': {
        'count': 10,
        'duration': 30 * 60,
    },
    'create-order-request': {
        'count': 10,
        'duration': 30 * 60,
    },
    'retail-store-stock': {
        'count': 10,
        'duration': 30 * 60
    },
    'cancellation-request-bulk-create': {
        'count': 1,
        'duration': 5,
    },
    'basket': {
        'count': 10,
        'duration': 60,
    },
    'create-address': {
        'count': 5,
        'duration': 60,
    },
    'user-permissions': {
        'count': 10,
        'duration': 60,
    },
    'appointment-date': {
        'count': 10,
        'duration': 60,
    },
    'user-segment': {
        'count': 3,
        'duration': 60
    },
}
```

### **131. Checkout Amount Control by Basket**

This setting controls whether the payment process checks only the basket amount or includes additional charges.

**Effect:**

* **True:** Payment is blocked if the basket total is 0. Only the basket items are considered.
* **False:** Payment is blocked only if the total amount, including basket items and additional charges such as shipping, gift wrap, etc., is 0.

Key: `CHECKOUT_AMOUNT_CONTROL_BY_BASKET`\
Type: Boolean\
Default: True

### **132. Email BCC and CC**

This setting enables dynamic configuration of BCC and CC email addresses for specific email templates.

**Effect:**

* Allows adding `bcc` and `cc` fields to outgoing emails per template.
* Default values are defined based on Email Templates with empty arrays.

Key: `EMAIL_BCC_AND_CC`\
Type: Object\
Default:

```
{
    "emails/loyalty/applied-loyalty-money": {
        "bcc": [],
        "cc": []
    },
    "emails/accounts/contact-us": {
        "bcc": [],
        "cc": []
    },
    "emails/orders/offer-accepted": {
        "bcc": [],
        "cc": []
    },
    "emails/orders/order-checkout-url": {
        "bcc": [],
        "cc": []
    },
    "emails/orders/order-ready-for-pickup": {
        "bcc": [],
        "cc": []
    },
    "emails/orders/cargo": {
        "bcc": [],
        "cc": []
    },
    "emails/orders/completed": {
        "bcc": [],
        "cc": []
    },
    "emails/orders/easy-refund-request": {
        "bcc": [],
        "cc": []
    },
    "emails/orders/order-delivered": {
        "bcc": [],
        "cc": []
    },
    "emails/orders/partial-cargo": {
        "bcc": [],
        "cc": []
    },
    "emails/orders/partial-delivered-cargo": {
        "bcc": [],
        "cc": []
    },
    "emails/orders/completed-subject": {
        "bcc": [],
        "cc": []
    },
    "emails/accounts/password-update-success-subject": {
        "bcc": [],
        "cc": []
    },
    "emails/accounts/contact-us-subject": {
        "bcc": [],
        "cc": []
    },
    "emails/users/conversation-replied": {
        "bcc": [],
        "cc": []
    },
    "emails/users/conversation-replied-subject": {
        "bcc": [],
        "cc": []
    },
    "emails/orders/package-created": {
        "bcc": [],
        "cc": []
    },
    "emails/accounts/password-update-success": {
        "bcc": [],
        "cc": []
    }
}
```

Example:

```
"emails/orders/order-delivered": {
  "bcc": ["bcc1@akinon.com", "bcc2@akinon.com"],
  "cc": ["cc1@akinon.com", "cc2@akinon.com"]
}
```
