Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

offer MYSQL_COMPATIBILITY setting to remove unique from the model #267

Merged
merged 8 commits into from
May 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 40 additions & 6 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ If you don't want default table appears in the DB then you should remove ``fcm_d

After setup your own ``Model`` don't forget to create ``migrations`` for your app and call ``migrate`` command.

After removing ``"fcm_django"`` out of ``INSTALLED_APPS``. You will need to re-register the Device in order to see it in the admin panel.
After removing ``"fcm_django"`` out of ``INSTALLED_APPS``. You will need to re-register the Device in order to see it in the admin panel.
This can be accomplished as follows at ``your_app/admin.py``:

.. code-block:: python
Expand All @@ -442,12 +442,46 @@ If you choose to move forward with swapped models then:
1. On existed project you have to keep in mind there are required manual work to move data from one table to anther.
2. If there's any tables with FK to swapped model then you have to deal with them on your own.

Note: This functionality based on `Swapper <https://pypi.org/project/swapper/>`_ that based on functionality
Note: This functionality based on `Swapper <https://pypi.org/project/swapper/>`_ that based on functionality
that allow to use a `custom User model <https://docs.djangoproject.com/en/4.2/topics/auth/customizing/#substituting-a-custom-user-model>`_.
So this functionality have the same limitations.
The most is important limitation it is that is difficult to start out with a default (non-swapped) model
So this functionality have the same limitations.
The most is important limitation it is that is difficult to start out with a default (non-swapped) model
and then later to switch to a swapped implementation without doing some migration hacking.

MySQL compatibility
-------------------
MySQL has a limit for indices and therefore the `registration_id` field cannot be made unique in MySQL.
We detect the database backend and remove the unique constraint for MySQL in the migration files. However,
to ensure that the constraint is removed from the actual model you have to add the following to your settings
to be able to run your django tests with MySQL and without running all migrations:

.. code-block:: python

FCM_DJANGO_SETTINGS = {
"MYSQL_COMPATIBILITY": True,
# [...] your other settings
}

As an alternative, you can use a custom model (see above) and either remove the unique constraint manually
or use a length limited CharField for the `registration_id` field. There are no guarantees on the max length of
FCM tokens, but in practice they are less than 200 characters long. Therefore, a CharField with a length of 600
should be sufficient and you can make it unique and index it even with MySQL:

.. code-block:: python

from fcm_django.models import AbstractFCMDevice, FCMDevice as OriginalFCMDevice


class CustomFCMDevice(AbstractFCMDevice):
registration_id = models.CharField(
verbose_name="Registration token",
unique=True,
max_length=600, # https://stackoverflow.com/a/64902685 better to be safe than sorry
)

class Meta(OriginalFCMDevice.Meta):
pass

Python 3 support
----------------
- ``fcm-django`` is fully compatible with Python 3.7+
Expand Down Expand Up @@ -481,11 +515,11 @@ Because there's possibility to use swapped models therefore tests contains two c
1. with default settings and non swapped models ``settings/default.py``
2. and with overwritten settings only that required by swapper - ``settings/swap.py``

To run tests locally you could use ``pytest``, and if you need to check migrations on different DB then you have to specify environment variable ``DATABASE_URL`` ie
To run tests locally you could use ``pytest``, and if you need to check migrations on different DB then you have to specify environment variable ``DATABASE_URL`` ie

.. code-block:: console

export DATABASE_URL=postgres://postgres:[email protected]:5432/postgres
export DJANGO_SETTINGS_MODULE=tests.settings.default
export DJANGO_SETTINGS_MODULE=tests.settings.default
# or export DJANGO_SETTINGS_MODULE=tests.settings.swap
pytest
24 changes: 15 additions & 9 deletions fcm_django/migrations/0010_unique_registration_id.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from django.db import migrations, models

from fcm_django.settings import FCM_DJANGO_SETTINGS as SETTINGS

_MYSQL = "mysql"


Expand Down Expand Up @@ -33,13 +35,17 @@ class Migration(migrations.Migration):
("fcm_django", "0009_alter_fcmdevice_user"),
]

operations = [
AlterFieldSkipMySQL(
model_name="fcmdevice",
name="registration_id",
field=models.TextField(
verbose_name="Registration token",
unique=True,
operations = (
[
AlterFieldSkipMySQL(
model_name="fcmdevice",
name="registration_id",
field=models.TextField(
verbose_name="Registration token",
unique=True,
),
),
),
]
]
if not SETTINGS["MYSQL_COMPATIBILITY"]
else []
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from django.db import migrations, models

from fcm_django.settings import FCM_DJANGO_SETTINGS as SETTINGS

_MYSQL = "mysql"


Expand Down Expand Up @@ -33,12 +35,16 @@ class Migration(migrations.Migration):
("fcm_django", "0010_unique_registration_id"),
]

operations = [
AddIndexSkipMySQL(
model_name="fcmdevice",
index=models.Index(
fields=["registration_id", "user"],
name="fcm_django__registr_dacdb2_idx",
operations = (
[
AddIndexSkipMySQL(
model_name="fcmdevice",
index=models.Index(
fields=["registration_id", "user"],
name="fcm_django__registr_dacdb2_idx",
),
),
),
]
]
if not SETTINGS["MYSQL_COMPATIBILITY"]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why explicitly check this? It should skip it if it's mysql anway?

Copy link
Contributor Author

@tuky tuky May 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make makemigrations happy. After adding

FCM_DJANGO_SETTINGS = {
    'MYSQL_COMPATIBILITY': True,
}

to the default test settings:

1st execution of makemigrations is with the check.
2nd execution is without it.

image

else []
)
9 changes: 5 additions & 4 deletions fcm_django/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ class AbstractFCMDevice(Device):
)
registration_id = models.TextField(
verbose_name=_("Registration token"),
unique=True,
unique=not SETTINGS["MYSQL_COMPATIBILITY"],
)
type = models.CharField(choices=DeviceType.choices, max_length=10)
objects: "FCMDeviceQuerySet" = FCMDeviceManager()
Expand Down Expand Up @@ -390,9 +390,10 @@ class Meta:
verbose_name = _("FCM device")
verbose_name_plural = _("FCM devices")

indexes = [
models.Index(fields=["registration_id", "user"]),
]
if not SETTINGS["MYSQL_COMPATIBILITY"]:
indexes = [
models.Index(fields=["registration_id", "user"]),
]

app_label = "fcm_django"
swappable = swapper.swappable_setting("fcm_django", "fcmdevice")
3 changes: 3 additions & 0 deletions fcm_django/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,6 @@
"invalid_package_name": "InvalidPackageName",
},
)

# MySQL compatibility
FCM_DJANGO_SETTINGS.setdefault("MYSQL_COMPATIBILITY", False)
Loading