{ "info": { "author": "Abirafdi Raditya Putra", "author_email": "raditya.putra@unit9.com", "bugtrack_url": null, "classifiers": [ "Environment :: Web Environment", "Framework :: Django", "Framework :: Django :: 2.2", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3.7", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content" ], "description": "# Django Data Sync\n\nEnables you to sync insensitive data (including FileField) between \nenvironments with any Django backends (as long the model definitions are the\nsame) directly from admin interface.\n\n## DISCLAIMER\n\nThere are no rigorous tests, yet. I haven't got the chance to explore how it \nbehaves with complex relationships.\nSo far, it has been used in two production grade\nprojects where the models are not too complex\n(ManyToMany is not yet properly tested).\n\nPlease use this at your own risk of data lost when syncing,\nor you can do rigorous testing at your development phase.\n\n## Features\n\n- enables you to sync insensitive data between the same Django environments \n (as long the model definitions are the same) directly from admin interface \n- relation fields are supported (ManyToMany needs to be tested)\n- synchronous sync or in background (only Cloud Tasks is supported)\n\nTO BE ADDED\n\n- ~~add support for ImageField and FileField~~ DONE\n- ~~support multiple tasks queues, current plan is to support GCP Cloud Tasks~~ DONE\n- add authorization and authentication at data export endpoint\n- add tests, since it's not possible to test with two Django servers locally \n (or there is?), I have to think how to implement this correctly\n\nMIGHT GET ADDED\n\n- compare data in JSON for audit purpose\n- add support for another tasks queues so that is cloud platform agnostic\n\n\n## Installation\n\n pip install django-data-sync\n\n\nadd `data_sync` to your `INSTALLED_APPS`\n\n```python\n ...\n ...\n\n 'data_sync',\n ....\n ....\n```\n\n\nRun migrate\n```text\npython manage.py migrate data_sync\n```\n\nAdd to urlpatterns. Please do take note of the prefix URLs it will be used \nlater.\ne.g. most likely we will include this in `api` App, thus the prefix is `/api`.\n```python\n path('', include('data_sync.urls')),\n```\n## Preface\n\nData Sync works by making use of natural key.\nSo I heavily recommend to read [django docs on this topic](https://docs.djangoproject.com/en/2.1/topics/serialization/#natural-keys) before going further.\n\nYou need to analyze your models and define their natural keys.\nYou can infer their natural keys usually from unique fields (and or `unique_together`).\n\nFields that are defined as unique or in `unique_together` can be defined by \nonly using the field name e.g. a Language is related to a Country.\nIn Language definition, \nthe `unique_together` is usually the Country + the Language's ISO 639-1.\n\nIn code it'll look something like this\n\n```python\nunique_together = (( 'country', 'code'),)\n```\n\nNotice that `country` in unique_together itself is _abstract_.\nWhat defines _a country_?\nIn context of `unique_together` it will be their ID, but ID is not natural key.\nCountry's natural key should be their ISO 2 code.\n\nSo we can infer that natural key of Language, programmatically, is \nthe Country's ISO 2 code + the Language's ISO 639-1 \n\nIt'll look like this when you implement in code\n```python\nclass Language(models.Model):\n def natural_key(self):\n return (self.country.code, self.code,)\n```\n\nIn essence, natural key is usually combination of unique fields and or \n`unique_together`, but it needs to be more _verbose_.\n\n## Usage\n\nTo get Data Sync working, you need to register the models that want to be \nsynced.\n**Only register insensitive models e.g. copy. Never sync sensitive \nmodels e.g. User as it can expose very sensitive data**.\n\nTo register the models, you need to decorate them and use custom managers.\n\n```python\nfrom django.db import models\n\nimport data_sync\n\n\n\n@data_sync.register_model(natural_key=['code'])\nclass Country(models.Model):\n objects = data_sync.managers.DataSyncEnhancedManager()\n\n code = models.CharField(max_length=2) # iso2\n ....\n ....\n\n\n@data_sync.register_model(natural_key=['country.code', 'code'])\nclass Language(models.Model):\n objects = data_sync.managers.DataSyncEnhancedManager()\n\n code = models.CharField(max_length=2) # iso 639-1\n ....\n ....\n\n\n@data_sync.register_model(\n natural_key=['language.country.code', 'language.code', 'key'],\n fields=('value', 'key', 'language'),\n file_fields=('thumbnail',)\n)\nclass Copy(models.Model):\n objects = data_sync.managers.DataSyncEnhancedManager()\n\n language = models.ForeignKey(Language, on_delete=models.CASCADE)\n value = models.TextField()\n key = models.CharField(max_length=255)\n default = models.TextField()\n thumbnail = models.ImageField()\n ....\n ....\n```\n\n### @data_sync.register_model\n\nHere you need to define your natural key (read Preface for further topic). \nIf natural key has value in related field, you need to use . (dot) notation.\n\nYou can also pass argument to `fields` parameter if you want to limit which \nfields that you want to be synced.\n\nTo add FileField into Data Sync, add them into `file_fields` parameter.\n\n### DataSyncEnhancedManager\n\nIt looks like manager initialization is done at class loading.\nSo adding custom manager programmatically might be considered hacky \n(I would really like to love input on this).\n\nFor now, I'm afraid you must define custom manager, with the default \nattribute name i.e. `objects` to use DataSyncEnhancedManager.\n\nDataSyncEnhancedManager just adds a `get_by_natural_key method` and no other \nelse.\n\n### Worker tasks\n\nWhen the code is deployed to GAE (and GAE only, flex and kube not supported yet),\n`data_sync` automatically uses Cloud Tasks with the queue id of `data_sync`.\n\n### Settings and Configuration\n\nData sync should work without additional settings \n(if using synchronous mode which is the default).\n\nIf you are deploying to GAE, it automatically uses Cloud Tasks, \nwhich you should fill the optionals below. \n\n#### Optionals\n\n DATA_SYNC_SERVICE_ACCOUNT_EMAIL\n\nDefaults to `` (empty string). You need to fill this with GCP service account.\nYou can use GAE default service account.\nIt is needed for OIDC validation as recommended\nby GCP.\n\n DATA_SYNC_FORCE_SYNC\n\nDefaults to `False`. Set this to `True` if you want to use synchronous\nwhen deployed to GAE.\n\n DATA_SYNC_CLOUD_TASKS_QUEUE_ID\n\nDefaults to `data_sync`\n\n DATA_SYNC_CLOUD_TASKS_LOCATION\n\nDefaults to `europe-west1`\n\n DATA_SYNC_GOOGLE_CLOUD_PROJECT\n\nDefaults to value of env var of `GOOGLE_CLOUD_PROJECT`.\n\n DATA_SYNC_GAE_VERSION\n\nDefaults to value of env var of `GAE_VERSION`, which is already set by GAE.\n\n DATA_SYNC_GAE_SERVICE\n\nDefaults to value of env var of `GAE_SERVICE`, which is already set by GAE.\n\n### Data Source\n\nData Source holds information about an environment from which you want your\ndata to be synced.\n\nThe URL is dependant on where and how you include the `data_sync.urls` at\ninstallation phase.\n\nFor example, if you include `data_sync.urls` in your `api` App urlpatterns,\nthen the URL in data source must be appended with your `api` URL.\nThus it might look something like this `https://example.com/api`.\n\nIf you include `data_sync.urls` in your root `urls`, then Data Source URL will\nlook like this `https://example.com`.\n\nDo not include endslash.\n\n### The Sync\n\nTo do a sync, simply create a Data Pull\n\n## Compatibility\n\nPython 3.7, Django 2.2 and up\n\n## Testing\n\nNo automated tests (yet.....).\n\nTo test locally, you can spawn two django servers with different ports and \ndifferent database and set the Data Source accordingly.\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://github.com/unit9/django-data-sync", "keywords": "", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "django-data-sync", "package_url": "https://pypi.org/project/django-data-sync/", "platform": "", "project_url": "https://pypi.org/project/django-data-sync/", "project_urls": { "Homepage": "http://github.com/unit9/django-data-sync" }, "release_url": "https://pypi.org/project/django-data-sync/0.5.2/", "requires_dist": [ "requests", "google-cloud-tasks (==1.1.0)", "cryptography (==2.7)", "PyJWT (==1.7.1)", "oidc-validators (==0.5.2)" ], "requires_python": ">=3.7", "summary": "Sync database between Django backends", "version": "0.5.2" }, "last_serial": 5718197, "releases": { "0.5.1": [ { "comment_text": "", "digests": { "md5": "f239fa040e9ed73df9989d0d15df1764", "sha256": "f215ae5bf0df73d6d70aa56c6aaf9ebe274bca0801443aa4f838ea5e316bb18a" }, "downloads": -1, "filename": "django_data_sync-0.5.1-py3-none-any.whl", "has_sig": false, "md5_digest": "f239fa040e9ed73df9989d0d15df1764", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.7", "size": 14493, "upload_time": "2019-08-23T01:13:49", "url": "https://files.pythonhosted.org/packages/9d/df/fc90641009a991ba036319a36a9438236352b969162e40d9bd173bb8983b/django_data_sync-0.5.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "80608f6ddaabae62d234048423fb8c76", "sha256": "4a278a8123479930953b44b03e653c4e7c2b378e035f7d14e1886232ecb2f7fe" }, "downloads": -1, "filename": "django_data_sync-0.5.1.tar.gz", "has_sig": false, "md5_digest": "80608f6ddaabae62d234048423fb8c76", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.7", "size": 11531, "upload_time": "2019-08-23T01:13:51", "url": "https://files.pythonhosted.org/packages/f7/60/a254b7ffb9848609f259bd3c2c0b8d77caefbd05638e3a19a3017d9230d8/django_data_sync-0.5.1.tar.gz" } ], "0.5.2": [ { "comment_text": "", "digests": { "md5": "a0ab68ec605a97af3b68e8b17209449b", "sha256": "d438e975604fc948a57b4917d9396d0478316992509afd0f720fbf64cbdb7edb" }, "downloads": -1, "filename": "django_data_sync-0.5.2-py3-none-any.whl", "has_sig": false, "md5_digest": "a0ab68ec605a97af3b68e8b17209449b", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.7", "size": 17413, "upload_time": "2019-08-23T01:43:06", "url": "https://files.pythonhosted.org/packages/75/4f/37279ad37feb32198bbf5b6544c8006252762c93b6305c074ecca56eae06/django_data_sync-0.5.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "1abe065feab6d5708702e2e948e4fb66", "sha256": "0ead9f371509c05b3e16849a7ab3103385a3ef4137a9194e8976f46e66b19a51" }, "downloads": -1, "filename": "django_data_sync-0.5.2.tar.gz", "has_sig": false, "md5_digest": "1abe065feab6d5708702e2e948e4fb66", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.7", "size": 15170, "upload_time": "2019-08-23T01:43:08", "url": "https://files.pythonhosted.org/packages/95/fb/5cfcb1ff7ed67c2db7f69544d2470dbe58c885287c3410bb5dccbaa771d4/django_data_sync-0.5.2.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "a0ab68ec605a97af3b68e8b17209449b", "sha256": "d438e975604fc948a57b4917d9396d0478316992509afd0f720fbf64cbdb7edb" }, "downloads": -1, "filename": "django_data_sync-0.5.2-py3-none-any.whl", "has_sig": false, "md5_digest": "a0ab68ec605a97af3b68e8b17209449b", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.7", "size": 17413, "upload_time": "2019-08-23T01:43:06", "url": "https://files.pythonhosted.org/packages/75/4f/37279ad37feb32198bbf5b6544c8006252762c93b6305c074ecca56eae06/django_data_sync-0.5.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "1abe065feab6d5708702e2e948e4fb66", "sha256": "0ead9f371509c05b3e16849a7ab3103385a3ef4137a9194e8976f46e66b19a51" }, "downloads": -1, "filename": "django_data_sync-0.5.2.tar.gz", "has_sig": false, "md5_digest": "1abe065feab6d5708702e2e948e4fb66", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.7", "size": 15170, "upload_time": "2019-08-23T01:43:08", "url": "https://files.pythonhosted.org/packages/95/fb/5cfcb1ff7ed67c2db7f69544d2470dbe58c885287c3410bb5dccbaa771d4/django_data_sync-0.5.2.tar.gz" } ] }