├── .env ├── .github └── workflows │ ├── lint.yml │ ├── release.yml │ └── test.yml ├── .pylintrc ├── LICENSE ├── MANIFEST.in ├── README.md ├── django_google_optimize ├── __init__.py ├── admin.py ├── apps.py ├── middleware.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py └── utils.py ├── docs ├── configuration.md ├── index.md ├── installation.md └── usage.md ├── manage.py ├── mkdocs.yml ├── mypy.ini ├── poetry.lock ├── pyproject.toml ├── tests ├── __init__.py ├── settings.py ├── templates │ └── context_processors │ │ └── test.html ├── test_helpers.py ├── test_middleware.py ├── test_utils.py ├── urls.py └── views.py └── tox.ini /.env: -------------------------------------------------------------------------------- 1 | export DJANGO_SETTINGS_MODULE=tests.settings 2 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Lint 3 | on: # yamllint disable-line rule:truthy 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | black: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v1 16 | - name: Set up Python 3.8 17 | uses: actions/setup-python@v1 18 | with: 19 | python-version: 3.8 20 | - name: Install dependencies 21 | run: | 22 | python -m pip install --upgrade pip 23 | pip install tox 24 | - name: Black with tox 25 | run: tox -e black 26 | isort: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v1 30 | - name: Set up Python 3.8 31 | uses: actions/setup-python@v1 32 | with: 33 | python-version: 3.8 34 | - name: Install dependencies 35 | run: | 36 | python -m pip install --upgrade pip 37 | pip install tox 38 | - name: Isort with tox 39 | run: tox -e isort 40 | pylint: 41 | runs-on: ubuntu-latest 42 | steps: 43 | - uses: actions/checkout@v1 44 | - name: Set up Python 3.8 45 | uses: actions/setup-python@v1 46 | with: 47 | python-version: 3.8 48 | - name: Install dependencies 49 | run: | 50 | python -m pip install --upgrade pip 51 | pip install tox 52 | - name: Pylint with tox 53 | run: tox -e pylint 54 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release 3 | on: # yamllint disable-line rule:truthy 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | lint: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Set up Python 3.9 13 | uses: actions/setup-python@v1 14 | with: 15 | python-version: 3.9 16 | - name: Install dependencies 17 | run: | 18 | python -m pip install --upgrade pip 19 | pip install tox 20 | - name: Black with tox 21 | run: tox -e black,isort,pylint 22 | test: 23 | runs-on: ubuntu-latest 24 | strategy: 25 | max-parallel: 3 26 | matrix: 27 | python-version: [3.8, 3.9] 28 | dj-version: [django22, django30, django31, djangomaster] 29 | steps: 30 | - uses: actions/checkout@v1 31 | - name: Set up Python ${{ matrix.python-version }} 32 | uses: actions/setup-python@v1 33 | with: 34 | python-version: ${{ matrix.python-version }} 35 | - name: Install dependencies 36 | run: | 37 | python -m pip install --upgrade pip 38 | pip install tox 39 | - name: Test with tox 40 | run: | 41 | PY_VERSION=${{ matrix.python-version }} && 42 | tox -e py${PY_VERSION//.}-${{ matrix.dj-version }} 43 | docs: 44 | runs-on: ubuntu-latest 45 | needs: [test, lint] 46 | steps: 47 | - uses: actions/checkout@v1 48 | - name: Set up Python 3.8 49 | uses: actions/setup-python@v1 50 | with: 51 | python-version: 3.8 52 | - name: Install dependencies 53 | run: | 54 | python -m pip install --upgrade pip 55 | pip install tox 56 | - name: Test with tox 57 | run: tox -e docs 58 | pip_package: 59 | runs-on: ubuntu-latest 60 | needs: [test, lint] 61 | steps: 62 | - uses: actions/checkout@v1 63 | - name: Set up Python 3.8 64 | uses: actions/setup-python@v1 65 | with: 66 | python-version: 3.8 67 | - name: Install dependencies 68 | run: | 69 | python -m pip install --upgrade pip 70 | pip install tox 71 | - name: Publish pip packages with tox 72 | run: tox -e release 73 | env: 74 | POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }} 75 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Test 3 | on: # yamllint disable-line rule:truthy 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | max-parallel: 3 16 | matrix: 17 | python-version: [3.8, 3.9] 18 | dj-version: [django22, django30, django31, djangomaster] 19 | 20 | steps: 21 | - uses: actions/checkout@v1 22 | - name: Set up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v1 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install tox 30 | - name: Test with tox 31 | run: | 32 | PY_VERSION=${{ matrix.python-version }} && 33 | tox -e py${PY_VERSION//.}-${{ matrix.dj-version }} 34 | - name: Upload coverage to Codecov 35 | uses: codecov/codecov-action@v1 36 | with: 37 | token: ${{ secrets.CODECOV_TOKEN }} 38 | file: ./coverage.xml 39 | flags: unittests 40 | name: ${{ matrix.python-version }}-${{ matrix.dj-version }} 41 | yml: ./codecov.yml 42 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # A comma-separated list of package or module names from where C extensions may 4 | # be loaded. Extensions are loading into the active Python interpreter and may 5 | # run arbitrary code. 6 | extension-pkg-whitelist= 7 | 8 | # Add files or directories to the blacklist. They should be base names, not 9 | # paths. 10 | ignore=CVS,migrations 11 | 12 | # Add files or directories matching the regex patterns to the blacklist. The 13 | # regex matches against base names, not paths. 14 | ignore-patterns= 15 | 16 | # Python code to execute, usually for sys.path manipulation such as 17 | # pygtk.require(). 18 | #init-hook= 19 | 20 | # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the 21 | # number of processors available to use. 22 | jobs=1 23 | 24 | # Control the amount of potential inferred values when inferring a single 25 | # object. This can help the performance when dealing with large functions or 26 | # complex, nested conditions. 27 | limit-inference-results=100 28 | 29 | # List of plugins (as comma separated values of python modules names) to load, 30 | # usually to register additional checkers. 31 | load-plugins=pylint_django 32 | 33 | # Pickle collected data for later comparisons. 34 | persistent=yes 35 | 36 | # Specify a configuration file. 37 | #rcfile= 38 | 39 | # When enabled, pylint would attempt to guess common misconfiguration and emit 40 | # user-friendly hints instead of false-positive error messages. 41 | suggestion-mode=yes 42 | 43 | # Allow loading of arbitrary C extensions. Extensions are imported into the 44 | # active Python interpreter and may run arbitrary code. 45 | unsafe-load-any-extension=no 46 | 47 | 48 | [MESSAGES CONTROL] 49 | 50 | # Only show warnings with the listed confidence levels. Leave empty to show 51 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. 52 | confidence= 53 | 54 | # Disable the message, report, category or checker with the given id(s). You 55 | # can either give multiple identifiers separated by comma (,) or put this 56 | # option multiple times (only on the command line, not in the configuration 57 | # file where it should appear only once). You can also use "--disable=all" to 58 | # disable everything first and then reenable specific checks. For example, if 59 | # you want to run only the similarities checker, you can use "--disable=all 60 | # --enable=similarities". If you want to run only the classes checker, but have 61 | # no Warning level messages displayed, use "--disable=all --enable=classes 62 | # --disable=W". 63 | disable=print-statement, 64 | parameter-unpacking, 65 | unpacking-in-except, 66 | old-raise-syntax, 67 | backtick, 68 | long-suffix, 69 | old-ne-operator, 70 | old-octal-literal, 71 | import-star-module-level, 72 | non-ascii-bytes-literal, 73 | raw-checker-failed, 74 | bad-inline-option, 75 | locally-disabled, 76 | file-ignored, 77 | suppressed-message, 78 | useless-suppression, 79 | deprecated-pragma, 80 | use-symbolic-message-instead, 81 | apply-builtin, 82 | basestring-builtin, 83 | buffer-builtin, 84 | cmp-builtin, 85 | coerce-builtin, 86 | execfile-builtin, 87 | file-builtin, 88 | long-builtin, 89 | raw_input-builtin, 90 | reduce-builtin, 91 | standarderror-builtin, 92 | unicode-builtin, 93 | xrange-builtin, 94 | coerce-method, 95 | delslice-method, 96 | getslice-method, 97 | setslice-method, 98 | no-absolute-import, 99 | old-division, 100 | dict-iter-method, 101 | dict-view-method, 102 | next-method-called, 103 | metaclass-assignment, 104 | indexing-exception, 105 | raising-string, 106 | reload-builtin, 107 | oct-method, 108 | hex-method, 109 | nonzero-method, 110 | cmp-method, 111 | input-builtin, 112 | round-builtin, 113 | intern-builtin, 114 | unichr-builtin, 115 | map-builtin-not-iterating, 116 | zip-builtin-not-iterating, 117 | range-builtin-not-iterating, 118 | filter-builtin-not-iterating, 119 | using-cmp-argument, 120 | eq-without-hash, 121 | div-method, 122 | idiv-method, 123 | rdiv-method, 124 | exception-message-attribute, 125 | invalid-str-codec, 126 | sys-max-int, 127 | bad-python3-import, 128 | deprecated-string-function, 129 | deprecated-str-translate-call, 130 | deprecated-itertools-function, 131 | deprecated-types-field, 132 | next-method-defined, 133 | dict-items-not-iterating, 134 | dict-keys-not-iterating, 135 | dict-values-not-iterating, 136 | deprecated-operator-function, 137 | deprecated-urllib-function, 138 | xreadlines-attribute, 139 | deprecated-sys-function, 140 | exception-escape, 141 | comprehension-escape, 142 | missing-docstring, 143 | invalid-name, 144 | logging-fstring-interpolation, 145 | too-many-ancestors, 146 | fixme, 147 | bad-continuation, 148 | no-self-use 149 | 150 | # Enable the message, report, category or checker with the given id(s). You can 151 | # either give multiple identifier separated by comma (,) or put this option 152 | # multiple time (only on the command line, not in the configuration file where 153 | # it should appear only once). See also the "--disable" option for examples. 154 | enable=c-extension-no-member 155 | 156 | 157 | [REPORTS] 158 | 159 | # Python expression which should return a note less than 10 (10 is the highest 160 | # note). You have access to the variables errors warning, statement which 161 | # respectively contain the number of errors / warnings messages and the total 162 | # number of statements analyzed. This is used by the global evaluation report 163 | # (RP0004). 164 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 165 | 166 | # Template used to display messages. This is a python new-style format string 167 | # used to format the message information. See doc for all details. 168 | #msg-template= 169 | 170 | # Set the output format. Available formats are text, parseable, colorized, json 171 | # and msvs (visual studio). You can also give a reporter class, e.g. 172 | # mypackage.mymodule.MyReporterClass. 173 | output-format=text 174 | 175 | # Tells whether to display a full report or only the messages. 176 | reports=no 177 | 178 | # Activate the evaluation score. 179 | score=yes 180 | 181 | 182 | [REFACTORING] 183 | 184 | # Maximum number of nested blocks for function / method body 185 | max-nested-blocks=5 186 | 187 | # Complete name of functions that never returns. When checking for 188 | # inconsistent-return-statements if a never returning function is called then 189 | # it will be considered as an explicit return statement and no message will be 190 | # printed. 191 | never-returning-functions=sys.exit 192 | 193 | 194 | [FORMAT] 195 | 196 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 197 | expected-line-ending-format= 198 | 199 | # Regexp for a line that is allowed to be longer than the limit. 200 | ignore-long-lines=^\s*(# )??$ 201 | 202 | # Number of spaces of indent required inside a hanging or continued line. 203 | indent-after-paren=4 204 | 205 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 206 | # tab). 207 | indent-string=' ' 208 | 209 | # Maximum number of characters on a single line. 210 | max-line-length=88 211 | 212 | # Maximum number of lines in a module. 213 | max-module-lines=1000 214 | 215 | # List of optional constructs for which whitespace checking is disabled. `dict- 216 | # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. 217 | # `trailing-comma` allows a space between comma and closing bracket: (a, ). 218 | # `empty-line` allows space-only lines. 219 | no-space-check=trailing-comma, 220 | dict-separator 221 | 222 | # Allow the body of a class to be on the same line as the declaration if body 223 | # contains single statement. 224 | single-line-class-stmt=no 225 | 226 | # Allow the body of an if to be on the same line as the test if there is no 227 | # else. 228 | single-line-if-stmt=no 229 | 230 | 231 | [BASIC] 232 | 233 | # Naming style matching correct argument names. 234 | argument-naming-style=snake_case 235 | 236 | # Regular expression matching correct argument names. Overrides argument- 237 | # naming-style. 238 | #argument-rgx= 239 | 240 | # Naming style matching correct attribute names. 241 | attr-naming-style=snake_case 242 | 243 | # Regular expression matching correct attribute names. Overrides attr-naming- 244 | # style. 245 | #attr-rgx= 246 | 247 | # Bad variable names which should always be refused, separated by a comma. 248 | bad-names=foo, 249 | bar, 250 | baz, 251 | toto, 252 | tutu, 253 | tata 254 | 255 | # Naming style matching correct class attribute names. 256 | class-attribute-naming-style=any 257 | 258 | # Regular expression matching correct class attribute names. Overrides class- 259 | # attribute-naming-style. 260 | #class-attribute-rgx= 261 | 262 | # Naming style matching correct class names. 263 | class-naming-style=PascalCase 264 | 265 | # Regular expression matching correct class names. Overrides class-naming- 266 | # style. 267 | #class-rgx= 268 | 269 | # Naming style matching correct constant names. 270 | const-naming-style=UPPER_CASE 271 | 272 | # Regular expression matching correct constant names. Overrides const-naming- 273 | # style. 274 | #const-rgx= 275 | 276 | # Minimum line length for functions/classes that require docstrings, shorter 277 | # ones are exempt. 278 | docstring-min-length=-1 279 | 280 | # Naming style matching correct function names. 281 | function-naming-style=snake_case 282 | 283 | # Regular expression matching correct function names. Overrides function- 284 | # naming-style. 285 | #function-rgx= 286 | 287 | # Good variable names which should always be accepted, separated by a comma. 288 | good-names=i, 289 | j, 290 | k, 291 | ex, 292 | Run, 293 | _ 294 | 295 | # Include a hint for the correct naming format with invalid-name. 296 | include-naming-hint=no 297 | 298 | # Naming style matching correct inline iteration names. 299 | inlinevar-naming-style=any 300 | 301 | # Regular expression matching correct inline iteration names. Overrides 302 | # inlinevar-naming-style. 303 | #inlinevar-rgx= 304 | 305 | # Naming style matching correct method names. 306 | method-naming-style=snake_case 307 | 308 | # Regular expression matching correct method names. Overrides method-naming- 309 | # style. 310 | #method-rgx= 311 | 312 | # Naming style matching correct module names. 313 | module-naming-style=snake_case 314 | 315 | # Regular expression matching correct module names. Overrides module-naming- 316 | # style. 317 | #module-rgx= 318 | 319 | # Colon-delimited sets of names that determine each other's naming style when 320 | # the name regexes allow several styles. 321 | name-group= 322 | 323 | # Regular expression which should only match function or class names that do 324 | # not require a docstring. 325 | no-docstring-rgx=^_ 326 | 327 | # List of decorators that produce properties, such as abc.abstractproperty. Add 328 | # to this list to register other decorators that produce valid properties. 329 | # These decorators are taken in consideration only for invalid-name. 330 | property-classes=abc.abstractproperty 331 | 332 | # Naming style matching correct variable names. 333 | variable-naming-style=snake_case 334 | 335 | # Regular expression matching correct variable names. Overrides variable- 336 | # naming-style. 337 | #variable-rgx= 338 | 339 | 340 | [TYPECHECK] 341 | 342 | # List of decorators that produce context managers, such as 343 | # contextlib.contextmanager. Add to this list to register other decorators that 344 | # produce valid context managers. 345 | contextmanager-decorators=contextlib.contextmanager 346 | 347 | # List of members which are set dynamically and missed by pylint inference 348 | # system, and so shouldn't trigger E1101 when accessed. Python regular 349 | # expressions are accepted. 350 | generated-members= 351 | 352 | # Tells whether missing members accessed in mixin class should be ignored. A 353 | # mixin class is detected if its name ends with "mixin" (case insensitive). 354 | ignore-mixin-members=yes 355 | 356 | # Tells whether to warn about missing members when the owner of the attribute 357 | # is inferred to be None. 358 | ignore-none=yes 359 | 360 | # This flag controls whether pylint should warn about no-member and similar 361 | # checks whenever an opaque object is returned when inferring. The inference 362 | # can return multiple potential results while evaluating a Python object, but 363 | # some branches might not be evaluated, which results in partial inference. In 364 | # that case, it might be useful to still emit no-member and other checks for 365 | # the rest of the inferred objects. 366 | ignore-on-opaque-inference=yes 367 | 368 | # List of class names for which member attributes should not be checked (useful 369 | # for classes with dynamically set attributes). This supports the use of 370 | # qualified names. 371 | ignored-classes=optparse.Values,thread._local,_thread._local 372 | 373 | # List of module names for which member attributes should not be checked 374 | # (useful for modules/projects where namespaces are manipulated during runtime 375 | # and thus existing member attributes cannot be deduced by static analysis. It 376 | # supports qualified module names, as well as Unix pattern matching. 377 | ignored-modules= 378 | 379 | # Show a hint with possible names when a member name was not found. The aspect 380 | # of finding the hint is based on edit distance. 381 | missing-member-hint=yes 382 | 383 | # The minimum edit distance a name should have in order to be considered a 384 | # similar match for a missing member name. 385 | missing-member-hint-distance=1 386 | 387 | # The total number of similar names that should be taken in consideration when 388 | # showing a hint for a missing member. 389 | missing-member-max-choices=1 390 | 391 | 392 | [SPELLING] 393 | 394 | # Limits count of emitted suggestions for spelling mistakes. 395 | max-spelling-suggestions=4 396 | 397 | # Spelling dictionary name. Available dictionaries: none. To make it working 398 | # install python-enchant package.. 399 | spelling-dict= 400 | 401 | # List of comma separated words that should not be checked. 402 | spelling-ignore-words= 403 | 404 | # A path to a file that contains private dictionary; one word per line. 405 | spelling-private-dict-file= 406 | 407 | # Tells whether to store unknown words to indicated private dictionary in 408 | # --spelling-private-dict-file option instead of raising a message. 409 | spelling-store-unknown-words=no 410 | 411 | 412 | [VARIABLES] 413 | 414 | # List of additional names supposed to be defined in builtins. Remember that 415 | # you should avoid defining new builtins when possible. 416 | additional-builtins= 417 | 418 | # Tells whether unused global variables should be treated as a violation. 419 | allow-global-unused-variables=yes 420 | 421 | # List of strings which can identify a callback function by name. A callback 422 | # name must start or end with one of those strings. 423 | callbacks=cb_, 424 | _cb 425 | 426 | # A regular expression matching the name of dummy variables (i.e. expected to 427 | # not be used). 428 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 429 | 430 | # Argument names that match this expression will be ignored. Default to name 431 | # with leading underscore. 432 | ignored-argument-names=_.*|^ignored_|^unused_ 433 | 434 | # Tells whether we should check for unused import in __init__ files. 435 | init-import=no 436 | 437 | # List of qualified module names which can have objects that can redefine 438 | # builtins. 439 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io 440 | 441 | 442 | [SIMILARITIES] 443 | 444 | # Ignore comments when computing similarities. 445 | ignore-comments=yes 446 | 447 | # Ignore docstrings when computing similarities. 448 | ignore-docstrings=yes 449 | 450 | # Ignore imports when computing similarities. 451 | ignore-imports=yes 452 | 453 | # Minimum lines number of a similarity. 454 | min-similarity-lines=7 455 | 456 | 457 | [MISCELLANEOUS] 458 | 459 | # List of note tags to take in consideration, separated by a comma. 460 | notes=FIXME, 461 | XXX, 462 | TODO 463 | 464 | 465 | [LOGGING] 466 | 467 | # Format style used to check logging format string. `old` means using % 468 | # formatting, while `new` is for `{}` formatting. 469 | logging-format-style=old 470 | 471 | # Logging modules to check that the string format arguments are in logging 472 | # function parameter format. 473 | logging-modules=logging 474 | 475 | 476 | [CLASSES] 477 | 478 | # List of method names used to declare (i.e. assign) instance attributes. 479 | defining-attr-methods=__init__, 480 | __new__, 481 | setUp 482 | 483 | # List of member names, which should be excluded from the protected access 484 | # warning. 485 | exclude-protected=_asdict, 486 | _fields, 487 | _replace, 488 | _source, 489 | _make 490 | 491 | # List of valid names for the first argument in a class method. 492 | valid-classmethod-first-arg=cls 493 | 494 | # List of valid names for the first argument in a metaclass class method. 495 | valid-metaclass-classmethod-first-arg=cls 496 | 497 | 498 | [DESIGN] 499 | 500 | # Maximum number of arguments for function / method. 501 | max-args=5 502 | 503 | # Maximum number of attributes for a class (see R0902). 504 | max-attributes=7 505 | 506 | # Maximum number of boolean expressions in an if statement. 507 | max-bool-expr=5 508 | 509 | # Maximum number of branch for function / method body. 510 | max-branches=12 511 | 512 | # Maximum number of locals for function / method body. 513 | max-locals=15 514 | 515 | # Maximum number of parents for a class (see R0901). 516 | max-parents=7 517 | 518 | # Maximum number of public methods for a class (see R0904). 519 | max-public-methods=20 520 | 521 | # Maximum number of return / yield for function / method body. 522 | max-returns=6 523 | 524 | # Maximum number of statements in function / method body. 525 | max-statements=50 526 | 527 | # Minimum number of public methods for a class (see R0903). 528 | min-public-methods=2 529 | 530 | 531 | [IMPORTS] 532 | 533 | # Allow wildcard imports from modules that define __all__. 534 | allow-wildcard-with-all=no 535 | 536 | # Analyse import fallback blocks. This can be used to support both Python 2 and 537 | # 3 compatible code, which means that the block might have code that exists 538 | # only in one or another interpreter, leading to false positives when analysed. 539 | analyse-fallback-blocks=no 540 | 541 | # Deprecated modules which should not be used, separated by a comma. 542 | deprecated-modules=optparse,tkinter.tix 543 | 544 | # Create a graph of external dependencies in the given file (report RP0402 must 545 | # not be disabled). 546 | ext-import-graph= 547 | 548 | # Create a graph of every (i.e. internal and external) dependencies in the 549 | # given file (report RP0402 must not be disabled). 550 | import-graph= 551 | 552 | # Create a graph of internal dependencies in the given file (report RP0402 must 553 | # not be disabled). 554 | int-import-graph= 555 | 556 | # Force import order to recognize a module as part of the standard 557 | # compatibility libraries. 558 | known-standard-library= 559 | 560 | # Force import order to recognize a module as part of a third party library. 561 | known-third-party=enchant 562 | 563 | 564 | [EXCEPTIONS] 565 | 566 | # Exceptions that will emit a warning when being caught. Defaults to 567 | # "Exception". 568 | overgeneral-exceptions=Exception 569 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2011-2019 Adin Hodovic and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md 3 | recursive-include docs * 4 | recursive-include google_optimize 5 | global-exclude __pycache__ 6 | global-exclude *.py[co] 7 | global-exclude .mypy_cache 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django-google-optimize 2 | 3 | ![Lint](https://github.com/adinhodovic/django-google-optimize/workflows/Test/badge.svg) 4 | ![Test](https://github.com/adinhodovic/django-google-optimize/workflows/Lint/badge.svg) 5 | [![Coverage](https://codecov.io/gh/adinhodovic/django-google-optimize/branch/master/graphs/badge.svg)](https://codecov.io/gh/adinhodovic/django-google-optimize/branch/master) 6 | [![Supported Python versions](https://img.shields.io/pypi/pyversions/django-google-optimize.svg)](https://pypi.org/project/django-google-optimize/) 7 | [![PyPI Version](https://img.shields.io/pypi/v/django-google-optimize.svg?style=flat)](https://pypi.org/project/django-google-optimize/) 8 | 9 | Django-google-optimize is a Django application designed to make running Google Optimize A/B tests easy. 10 | 11 | [Here is a tutorial on the Google Optimize basics and how to use this Django application.](https://hodovi.cc/blog/django-b-testing-google-optimize/) 12 | 13 | ## Installation 14 | 15 | Install django-google-optimize with pip: 16 | 17 | `pip install django-google-optimize` 18 | 19 | Add the application to installed Django applications: 20 | 21 | ```py 22 | # settings.py 23 | INSTALLED_APPS = [ 24 | ... 25 | "django_google_optimize", 26 | ... 27 | ] 28 | ``` 29 | 30 | Add the middleware: 31 | 32 | ```py 33 | MIDDLEWARE = [ 34 | ... 35 | "django_google_optimize.middleware.google_optimize", 36 | ... 37 | ] 38 | ``` 39 | 40 | ## Getting started 41 | 42 | Head over to the Django admin and add a new Google Optimize experiment. Add an experiment variant with the index 1 and the alias "new_design". Set the experiment cookie's active variant index to 1. Now the active variant index for that experiment is 1 which is the experiment variant with the alias "new_design" that you created. 43 | 44 | Now you can access the experiment in templates by the experiment alias and the variant alias: 45 | 46 | ```django 47 | {% if request.google_optimize.redesign == "new_design" %} 48 | {% include "jobs/jobposting_list_new.html" %} 49 | {% else %} 50 | {% include "jobs/jobposting_list_old.html" %} 51 | {% endif %} 52 | ``` 53 | 54 | Or use it inline: 55 | 56 | ```django 57 |