{ "info": { "author": "monotonee", "author_email": "monotonee@tuta.io", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Programming Language :: Python :: 3 :: Only", "Topic :: Database" ], "description": "###################\ndjango-forcedfields\n###################\n\n.. image:: https://img.shields.io/pypi/v/django-forcedfields.svg\n :target: https://pypi.python.org/pypi/django-forcedfields\n :align: left\n.. image:: https://travis-ci.org/monotonee/django-forcedfields.svg?branch=master\n :target: https://travis-ci.org/monotonee/django-forcedfields\n :align: right\n\n*******\nSummary\n*******\n\nA Python module that provides a set of custom, specialized Django model fields.\n\nWhile I haved worked with Django's ORM for some time and have enjoyed many of its features for\nsimple use cases, I find myself increasingly impeded, annoyed, and dissatisfied by its limitations\nin complex applications. One glaring problem in my eyes is the ORM's lack of semantic database field\ndata types and modifiers.\n\nFor example, an eight-character varchar field that can be null and that has a default value of\n\"elegy\" will *not* result in the MySQL `DDL\n`_::\n\n VARCHAR(8) DEFAULT 'elegy' NULL\n\nbut simply as::\n\n VARCHAR(8) NULL\n\nWhile this varchar example may not be the most egregious, it nonetheless illustrates the almost\ncomplete reliance upon the application and its ORM for behavior that should be handled, and indeed\nis best handled, by the database management system itself.\n\nDatabases should be as self-documenting and semantic as possible, independent of any application\ncode, ORM models, or documentation. I will not compromise this principle for the sake of an ORM's\nconveniences. To this end, I have begun to create these custom Django model fields to force Django\nto issue the most specific and complete DDL statements possible. It is my goal with these and future\nfields to shift responsibility from the application ORM to the underlying database wherever possible\nwhile maintaining a consistent and complete ORM interface and database backend abstraction.\n\n************\nInstallation\n************\n::\n\n pip install [--user] django-forcedfields\n\n*************\nExample Usage\n*************\n::\n\n import django_forcedfields as forcedfields\n\nor::\n\n from django_forcedfields import TimestampField\n\n******\nFields\n******\n\nFixedCharField\n==============\n\n**class FixedCharField(max_length=None, **options)**\n\nThis field extends Django's `CharField\n`_.\n\nThis field inherits all functionality and interfaces from Django's standard CharField but, rather\nthan producing a ``VARCHAR`` field in the database, the FixedCharField creates a ``CHAR`` field. The\nparent CharField class' keyword argument ``max_length`` is retained and, when passed, specifies the\n``CHAR`` field's max length just like it does for the ``VARCHAR`` implementation. The ``CHAR`` data\ntype is supported on all RDBMS in common use with Django.\n\nIn addition, if a FixedCharField on a model is not given an explicit value and no default field\nvalue has been explicitly defined, a ``NULL`` value will be inserted on Model.save(). This is in\ncontrast to Django's standard CharField which incorrectly attempts to insert an empty string in such\na case. Ideally, with no explicit value and no default value, an integrity error would be raised by\nthe database but Django's ORM absolutely requires a value for all fields in ``INSERT`` operations.\nIt is impossible to simply omit a database column's value in an ``INSERT`` statement.\n\nA note here on Django's `admonition on null values with text fields\n`_: Django is wrong. ``NULL`` means\nunknown data, an empty string means an empty string. Their meanings are *semantically different* by\ndefinition. Set ``null=True`` on text fields when your use case warrants it. That is, when you may\nhave a complete absence of data as well as the need to record an empty string. Google this topic\nfor more analysis.\n\nTimestampField\n==============\n\n**class TimestampField(auto_now=False, auto_now_add=False, auto_now_update=False, **options)**\n\nThis field extends Django's `DateTimeField\n`_.\n\nThis field supports all `DateTimeField keyword arguments\n`_ and adds a new\n``auto_now_update`` argument.\n\n**TimestampField.auto_now_update**\n ``auto_now_update`` is a boolean that, when True, sets a new timestamp field value on update\n operations *only*, not on insert.\n\n This option is mutually exclusive with ``auto_now``.\n\n**Warning:** When using the MySQL backend, the database ``TIMESTAMP`` field will also be updated\nwhen ``auto_now`` or ``auto_now_update`` is enabled and when calling QuerySet.update(). In\nconstrast, Django's DateField and DateTimeField only set current timestamp under ``auto_now`` `when\ncalling Model.save()\n`_.\nThis modified behavior is due to the declaration of ``ON UPDATE CURRENT_TIMESTAMP`` in the\nTimestampField's column definition DDL.\n\nLike its parent DateTimeField, the TimestampField's options ``auto_now``, ``auto_now_add``, and\n``auto_now_update`` will forcibly overwrite any manually-set model field attribute values when\nenabled and when their conditions are triggered. The value will be the Django ORM database function\n`Now()\n`_ rather than a datetime\ninstance since the value will have been generated by the database server and must therefore be\nretrieved with a separate query.\n\nNaturally, when designing a system field instead of a user data field, the need to offload\nresponsibility to the underlying database becomes greater. If the data is for system and metadata\npurposes, then it increases consistency and data integrity to delegate field value management to the\nsystem itself.\n\nA timestamp is well-suited to record system and database record metadata such as record insert and\nupdate times. Due to the database data type features, it is also ideal when storing a fixed point in\ntime, independent of time zone. Although the creation of the TimestampField was largely motivated by\nthe need for an ORM abstraction for metadata fields, it can also be used just like its parent\nDateTimeField as long as one understands the data type's different advantages and limitations.\n\nInstead of DateTimeField's reliance on ``DATETIME`` and similar data types, the TimestampField uses\n``TIMESTAMP`` data type and other data types that do not store time zone information. The data type\nchanges can be seen in the following table:\n\n========== ======================= ===========================\ndatabase DateTimeField data type TimestampField data type\n========== ======================= ===========================\nMySQL DATETIME TIMESTAMP\nPostgreSQL TIMESTAMP WITH TIMEZONE TIMESTAMP WITHOUT TIME ZONE\nSQLite DATETIME DATETIME\n========== ======================= ===========================\n\nAlso note that standard DDL modifiers such as ``DEFAULT CURRENT TIMESTAMP`` and non-standard ones\nsuch as MySQL's ``ON UPDATE CURRENT_TIMESTAMP`` are used when the corresponding options on a\nTimestampField instance are enabled.\n\n******************************\nDatabase Engine Considerations\n******************************\n\nWhen using TimestampField, one must be aware of certain database engine behavior defaults and\nconfigurations. An ORM is usually designed to abstract, as much as is practical and prudent, the\ndifferences between the underlying databases. In this case, however, the abstraction leaks. Consider\nthe following timestamp column DDL::\n\n TIMESTAMP NOT NULL\n\nNote the lack of a ``DEFAULT`` clause. One would expect, upon attempting to insert a ``NULL`` value\nor failing to provide a value for the column altogether, that some sort of constraint or integrity\nexception would be raised. Indeed, this behavior adheres to the principle of least astonishment and\nis the standard behavior of both SQLite and PostgreSQL. Both `SQLite\n`_ and `PostgreSQL\n`_ implicitly assign\n``DEFAULT NULL`` to column definitions with no explicit ``DEFAULT`` clause.\n\nMySQL\n=====\n\nMySQL requires a specific configuration to achieve the same standard behavior. The following\nconfiguration options affect ``TIMESTAMP`` columns:\n\n- `strict mode `_\n- `NO_ZERO_DATE `_\n- `NO_ZERO_IN_DATE `_\n- `explicit_defaults_for_timestamp `_\n\nAt minimum, MySQL requires that both strict mode and ``explicit_defaults_for_timestamp`` are\nenabled for ``TIMESTAMP`` behavior to conform to standards. If one attempts to omit a value for the\n``TIMESTAMP NOT NULL`` column, a \"ERROR 1364 (HY000): Field doesn't have a default\nvalue\" is emitted and if one attempts to insert a ``NULL`` value, a \"ERROR 1048 (23000): Column\n cannot be null\" is emitted. As of version MySQL 5.7, strict mode is enabled by default\nbut ``explicit_defaults_for_timestamp`` is not.\n\nMariaDB\n=======\n\nMariaDB, on the other hand, applies the same configuration parameters in a different way and its\nlogic as it relates to ``TIMESTAMP NOT NULL`` is less clear and, dare I say, erroneous. Assuming\nidentical configuration (strict mode and ``explicit_defaults_for_timestamp`` enabled), MariaDB\nraises \"ERROR 1364 (HY000): Field doesn't have a default value\" on insert value\nomission but successfully accepts a ``NULL`` value with no error and stores the results of\n``CURRENT_TIMESTAMP()`` in the field instead.\n\nIn an attempt to bring MariaDB in line with the standard, I also tested ``NO_ZERO_DATE`` and\n``NO_ZERO_IN_DATE``. As long as both ``explicit_defaults_for_timestamp`` and ``NO_ZERO_DATE`` or\n``NO_ZERO_IN_DATE`` are enabled, it is impossible to create a table containing the\n``TIMESTAMP NOT NULL`` column as the ``CREATE TABLE`` statement fails with \"ERROR 1067 (42000):\nInvalid default value for \". This suggests that not only is the ``DEFAULT`` value\nvalidated during DDL statements, but MariaDB is also attempting to implicitly define a zero value\n``DEFAULT`` value on the ``TIMESTAMP`` field as the same error is raised when\n``DEFAULT '0000-00-00 00:00:00'`` is explicitly defined. This is nonstandard, erroneous behavior and\nconflicts with that of MySQL. From the `MySQL documentation\n`_:\n\n ``TIMESTAMP`` columns explicitly declared with the ``NOT NULL`` attribute and without an\n explicit ``DEFAULT`` attribute are treated as having no default value.\n\nFrom the same documentation page, the following governs ``INSERT`` operations under these\nconditions:\n\n For inserted rows that specify no explicit value for such a column, the result depends on the\n SQL mode. If strict SQL mode is enabled, an error occurs. If strict SQL mode is not enabled, the\n column is declared with the implicit default of '0000-00-00 00:00:00' and a warning occurs. This\n is similar to how MySQL treats other temporal types such as DATETIME.\n\nThe DDL validation failure may have something to do with these ``INSERT`` rules.\n\nIt is impossible for MariaDB's ``TIMESTAMP`` fields to behave in a standard way when dealing with\n``TIMESTAMP NOT NULL`` columns. I found `this bug report\n`_ for MariaDB but it appears that the work has ceased\nand the fix has not been merged into the target release. All tests were performed on MariaDB 10.2\nand 10.3.\n\nConclusion\n==========\n\nI now have a choice to make: do I cause TimestampField to step aside and let the user more directly\nexperience the effects of the underlying database engine's configuration or do I attempt to abstract\nthe behavior differences as much as possible? Given the spirit and goal of this library, I have\nopted for less abstraction and have removed any additional, artificial normalization of database\nengine behavior in these field classes. I am certainly open to discussion on this point so please\ndon't hesitate to open communication with me or point out any errors in my testing.\n\nGiven MariaDB's deviation from standards, this package's unit tests are performed using MySQL and\ntesting on MariaDB is disabled until further notice.\n\nAs an aside, please note that many inconsistent behaviors between database engines can be mitigated\nor even eliminated by explicitly defining field keyword arguments such as ``default``, ``null``,\netc., causing more explicit DDL SQL to be generated by Django in the resulting migrations and SQL.\n\n***********\nDevelopment\n***********\n\nTo set up the development environment, a Vagrantfile is included. Install `Vagrant\n`_ and::\n\n vagrant up\n\nOnce Vagrant has completed provisioning, ``vagrant ssh`` into the box and start the database servers\nagainst which to run the test suite::\n\n docker-compose up -d\n\nFinally, run the tests with::\n\n make tests\n\nThe Vagrant machine is provisioned to use the UTC time zone to facilitate tests. If you elect to run\ntests outside of the Vagrant machine, be aware that certain tests assume identical time, date, and\ntime zone settings between all database engines. SQLite defaults to the host's localtime while the\nDocker containers use the host's clock and default to the UTC time zone.\n\nIn this project, I use PEP8 and `Google's Python style guide\n`_. Pylint doesn't play nicely with some of the\nstyles. A few notes on pylint:\n\n* bad-continuation\n\n * Ignore most of these. Google style guide allows for a 4-space hanging indent with nothing on\n first line.\n * Example: `indentation\n `_\n\n**************\nOracle Support\n**************\n\nThe FixedCharField should work on Oracle but the TimestampField will default to DateTimeField\ndatabase field data types when used with Oracle. I neither implemented functionality for nor tested\non Oracle for a few reasons:\n\n#. It is too difficult to get an Oracle server instance against which to test. As one can see, I use\n lightweight Docker containerized services to run the test databases. To use Oracle, one needs to\n provide the Oracle installation binaries. To get the binaries, one needs to sign in to Oracle's\n web site for the privilege of downloading over 2.5 gigabytes. Too much unnecessary pain, not\n enough return. If you use Oracle products, I sympathize and may god have mercy on your soul.\n\n * https://github.com/oracle/docker-images/tree/master/OracleDatabase\n\n#. Oracle seems to be `rarely used with Django `_.\n#. I hate Oracle products and Oracle as an entity.\n\n*********\nChangelog\n*********\n\nv1.0\n====\n\n* Automatic values from ``auto_now``, ``auto_now_add``, and ``auto_now_update`` are no longer\n generated in the application using ``datetime.datetime.now()`` or ``django.utils.timezone.now()``.\n ``CURRENT_TIMESTAMP`` generation is now performed by the database using the Django database\n function `django.db.models.functions.Now\n `_.\n* All fields now cause the ORM to issue explicit ``DEFAULT`` clauses in column DDL statements where\n previously the ORM always omitted ``DEFAULT`` clauses from column definitions. ``DEFAULT`` clauses\n will be defined in DDL if Field.has_default() returns True. This behavior naturally includes the\n generation of ``DEFAULT NULL`` in the column DDL if the field's ``default`` option is set\n to ``None``.\n* If no kwargs (options) are passed to TimestampField, no ``DEFAULT`` clause is generated in the\n column DDL for MySQL. Previously, a ``DEFAULT NULL`` or ``DEFAULT 0`` clause was output in the DDL\n to disable MySQL's default ``TIMESTAMP`` behavior. Howver, default ``TIMESTAMP`` behavior varies\n according to certain server system variables and, depending upon configuration, it may be\n completely valid to omit a ``DEFAULT`` clause altogether.\n* FixedCharField will now attempt to insert ``NULL`` if no value is defined on the model's field\n attribute and no explicit field default value has been defined. This behavior is in contrast to\n Django's standard CharField which always attempts to (incorrectly) store an empty string in such a\n case.\n\n\n", "description_content_type": null, "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/monotonee/django-forcedfields", "keywords": "char database django field model timestamp", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "django-forcedfields", "package_url": "https://pypi.org/project/django-forcedfields/", "platform": "", "project_url": "https://pypi.org/project/django-forcedfields/", "project_urls": { "Homepage": "https://github.com/monotonee/django-forcedfields" }, "release_url": "https://pypi.org/project/django-forcedfields/1.0.1/", "requires_dist": [ "django", "docker-compose; extra == 'dev'", "mysqlclient; extra == 'dev'", "psycopg2-binary; extra == 'dev'", "pylint; extra == 'dev'", "twine; extra == 'dev'", "wheel; extra == 'dev'" ], "requires_python": "", "summary": "Django model fields designed to more precisely and semantically define data types.", "version": "1.0.1" }, "last_serial": 3585643, "releases": { "0.1.1": [ { "comment_text": "", "digests": { "md5": "13aaad5e4ad456161caa40f200e7b8e7", "sha256": "25910c9a5c7d61f0a4dadf12e7e12ba3668fcc895b82cc5a8cd6f61fb50b04a4" }, "downloads": -1, "filename": "django_forcedfields-0.1.1-py3-none-any.whl", "has_sig": false, "md5_digest": "13aaad5e4ad456161caa40f200e7b8e7", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 14129, "upload_time": "2017-03-11T01:44:23", "url": "https://files.pythonhosted.org/packages/24/19/f6a41ed510cb21036a633e8c9fb3e955bf4db8115947fb9eaf41b0997240/django_forcedfields-0.1.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "db24073f397559b4f8e2e72064e7faf1", "sha256": "ffd0008b3b52c9a0762e8c13aeebe3e8f62631ab0261ebbca6f6859ccb817932" }, "downloads": -1, "filename": "django-forcedfields-0.1.1.tar.gz", "has_sig": false, "md5_digest": "db24073f397559b4f8e2e72064e7faf1", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 10366, "upload_time": "2017-03-11T01:44:25", "url": "https://files.pythonhosted.org/packages/72/fe/e0d91c5e5558b54b57c696b52c47dea1e3c67133977c964567884550420c/django-forcedfields-0.1.1.tar.gz" } ], "1.0.0": [ { "comment_text": "", "digests": { "md5": "585be1d91b65b4071d0a297972dc7d82", "sha256": "af96f418a4a16608bf2fd16221a0b3a1e5fd63974e47917ae9d0aea93caa6549" }, "downloads": -1, "filename": "django_forcedfields-1.0.0-py3-none-any.whl", "has_sig": false, "md5_digest": "585be1d91b65b4071d0a297972dc7d82", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 21472, "upload_time": "2017-11-13T18:37:13", "url": "https://files.pythonhosted.org/packages/5c/3a/0e2f9f30074950a41ecfae498e75f714499129ccf484e519271b2b859f57/django_forcedfields-1.0.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "c5c8201aadce2de96bcdabd0aa9de9e6", "sha256": "192ae8233cde647a7d1ecfe3eb3f06115d10e14857aabb0d1e952ceceb545238" }, "downloads": -1, "filename": "django-forcedfields-1.0.0.tar.gz", "has_sig": false, "md5_digest": "c5c8201aadce2de96bcdabd0aa9de9e6", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 15200, "upload_time": "2017-11-13T18:37:15", "url": "https://files.pythonhosted.org/packages/fd/f7/d936fb722e16245e7c77ca46b6c576d132cec922c2abced60afe4a122bcc/django-forcedfields-1.0.0.tar.gz" } ], "1.0.1": [ { "comment_text": "", "digests": { "md5": "ebec25d8bd75b0256ce0bb44834ebff2", "sha256": "67c891d11f30e62051c48c4c22ccbf1e392d716685abb8807ef40d3982fe52f6" }, "downloads": -1, "filename": "django_forcedfields-1.0.1-py3-none-any.whl", "has_sig": false, "md5_digest": "ebec25d8bd75b0256ce0bb44834ebff2", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 21701, "upload_time": "2018-02-15T21:36:54", "url": "https://files.pythonhosted.org/packages/3e/12/2fb0827ac05861613e70b8da3eb90e2e7758955f99f2c8fb6f5d274eac1d/django_forcedfields-1.0.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "e2dc2ea07e7656fa6a82b57f17ed2278", "sha256": "5fb04dc4f2e17a5e4ee326dd4c9eca1f178ef977a892f0fa710f796a910f24b9" }, "downloads": -1, "filename": "django-forcedfields-1.0.1.tar.gz", "has_sig": false, "md5_digest": "e2dc2ea07e7656fa6a82b57f17ed2278", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 15376, "upload_time": "2018-02-15T21:36:57", "url": "https://files.pythonhosted.org/packages/14/01/035fc6a18e1184817dca3324fee8853400431fb7c69fffe7de011806d508/django-forcedfields-1.0.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "ebec25d8bd75b0256ce0bb44834ebff2", "sha256": "67c891d11f30e62051c48c4c22ccbf1e392d716685abb8807ef40d3982fe52f6" }, "downloads": -1, "filename": "django_forcedfields-1.0.1-py3-none-any.whl", "has_sig": false, "md5_digest": "ebec25d8bd75b0256ce0bb44834ebff2", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 21701, "upload_time": "2018-02-15T21:36:54", "url": "https://files.pythonhosted.org/packages/3e/12/2fb0827ac05861613e70b8da3eb90e2e7758955f99f2c8fb6f5d274eac1d/django_forcedfields-1.0.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "e2dc2ea07e7656fa6a82b57f17ed2278", "sha256": "5fb04dc4f2e17a5e4ee326dd4c9eca1f178ef977a892f0fa710f796a910f24b9" }, "downloads": -1, "filename": "django-forcedfields-1.0.1.tar.gz", "has_sig": false, "md5_digest": "e2dc2ea07e7656fa6a82b57f17ed2278", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 15376, "upload_time": "2018-02-15T21:36:57", "url": "https://files.pythonhosted.org/packages/14/01/035fc6a18e1184817dca3324fee8853400431fb7c69fffe7de011806d508/django-forcedfields-1.0.1.tar.gz" } ] }