-
Notifications
You must be signed in to change notification settings - Fork 291
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
Fix duplicate orders for user notification policies #2278
Merged
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
219d549
Add OrderedModel
vadimkerr 362bab5
Use OrderedModel in UserNotificationPolicy
vadimkerr 32dfeb9
Update internal API view and serializers
vadimkerr 21601aa
Update public API
vadimkerr 08bc4a0
delete excess if
vadimkerr 230fa50
increase dev mysql max_connections
vadimkerr 20ff1db
Add comments
vadimkerr af50a5e
typing + pk name
vadimkerr 0e989a5
comment
vadimkerr 1c7aab9
comment
vadimkerr e0c8dcd
Merge branch 'dev' into vadimkerr/ordered-model
vadimkerr b93cf70
--no-migrations
vadimkerr 637b979
use connection.ops.quote_name
vadimkerr e8cf3d3
remove raw SQL
vadimkerr 6bc753a
Merge branch 'dev' into vadimkerr/ordered-model
vadimkerr b73ff95
changelog
vadimkerr 00fb5f2
ignore migration
vadimkerr c790132
create_default_policies_for_user
vadimkerr 5bca007
create_default_policies_for_user
vadimkerr 93c5588
test
vadimkerr cb4d5ba
lock queryset
vadimkerr 53a61c1
less retries
vadimkerr 45c6997
Merge branch 'dev' into vadimkerr/ordered-model
vadimkerr 004c095
_adjust_order
vadimkerr 294a8e6
more tests
vadimkerr e9a73bb
more tests
vadimkerr 1fb035d
Merge remote-tracking branch 'origin/vadimkerr/ordered-model' into va…
vadimkerr File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,7 @@ | |
from apps.base.models.user_notification_policy import NotificationChannelAPIOptions | ||
from apps.user_management.models import User | ||
from common.api_helpers.custom_fields import OrganizationFilteredPrimaryKeyRelatedField | ||
from common.api_helpers.exceptions import BadRequest, Forbidden | ||
from common.api_helpers.exceptions import Forbidden | ||
from common.api_helpers.mixins import EagerLoadingMixin | ||
|
||
|
||
|
@@ -34,6 +34,7 @@ class UserNotificationPolicyBaseSerializer(EagerLoadingMixin, serializers.ModelS | |
class Meta: | ||
model = UserNotificationPolicy | ||
fields = ["id", "step", "order", "notify_by", "wait_delay", "important", "user"] | ||
read_only_fields = ["order"] | ||
|
||
def to_internal_value(self, data): | ||
if data.get("wait_delay", None): | ||
|
@@ -67,7 +68,6 @@ def _notify_by_to_representation(self, instance, result): | |
|
||
|
||
class UserNotificationPolicySerializer(UserNotificationPolicyBaseSerializer): | ||
prev_step = serializers.CharField(required=False, write_only=True, allow_null=True) | ||
user = OrganizationFilteredPrimaryKeyRelatedField( | ||
queryset=User.objects, | ||
required=False, | ||
|
@@ -80,36 +80,16 @@ class UserNotificationPolicySerializer(UserNotificationPolicyBaseSerializer): | |
default=NotificationChannelAPIOptions.DEFAULT_NOTIFICATION_CHANNEL, | ||
) | ||
|
||
class Meta(UserNotificationPolicyBaseSerializer.Meta): | ||
fields = [*UserNotificationPolicyBaseSerializer.Meta.fields, "prev_step"] | ||
read_only_fields = ("order",) | ||
|
||
def create(self, validated_data): | ||
prev_step = validated_data.pop("prev_step", None) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
user = validated_data.get("user") | ||
user = validated_data.get("user") or self.context["request"].user | ||
organization = self.context["request"].auth.organization | ||
|
||
if not user: | ||
user = self.context["request"].user | ||
|
||
self_or_admin = user.self_or_admin(user_to_check=self.context["request"].user, organization=organization) | ||
if not self_or_admin: | ||
raise Forbidden() | ||
|
||
if prev_step is not None: | ||
try: | ||
prev_step = UserNotificationPolicy.objects.get(public_primary_key=prev_step) | ||
except UserNotificationPolicy.DoesNotExist: | ||
raise BadRequest(detail="Prev step does not exist") | ||
if prev_step.user != user or prev_step.important != validated_data.get("important", False): | ||
raise BadRequest(detail="UserNotificationPolicy can be created only with the same user and importance") | ||
instance = UserNotificationPolicy.objects.create(**validated_data) | ||
instance.to(prev_step.order + 1) | ||
return instance | ||
else: | ||
instance = UserNotificationPolicy.objects.create(**validated_data) | ||
return instance | ||
instance = UserNotificationPolicy.objects.create(**validated_data) | ||
return instance | ||
|
||
|
||
class UserNotificationPolicyUpdateSerializer(UserNotificationPolicyBaseSerializer): | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# Generated by Django 3.2.19 on 2023-06-16 15:10 | ||
|
||
from django.db import migrations, models | ||
from django.db.models import Count | ||
|
||
from common.database import get_random_readonly_database_key_if_present_otherwise_default | ||
import django_migration_linter as linter | ||
|
||
|
||
def fix_duplicate_order_user_notification_policy(apps, schema_editor): | ||
UserNotificationPolicy = apps.get_model('base', 'UserNotificationPolicy') | ||
|
||
# it should be safe to use a readonly database because duplicates are pretty infrequent | ||
db = get_random_readonly_database_key_if_present_otherwise_default() | ||
|
||
# find all (user_id, important, order) tuples that have more than one entry (meaning duplicates) | ||
items_with_duplicate_orders = UserNotificationPolicy.objects.using(db).values( | ||
"user_id", "important", "order" | ||
).annotate(count=Count("order")).order_by().filter(count__gt=1) # use order_by() to reset any existing ordering | ||
|
||
# make sure we don't fix the same (user_id, important) pair more than once | ||
values_to_fix = set((item["user_id"], item["important"]) for item in items_with_duplicate_orders) | ||
|
||
for user_id, important in values_to_fix: | ||
policies = UserNotificationPolicy.objects.filter(user_id=user_id, important=important).order_by("order", "id") | ||
# assign correct sequential order for each policy starting from 0 | ||
for idx, policy in enumerate(policies): | ||
policy.order = idx | ||
UserNotificationPolicy.objects.bulk_update(policies, fields=["order"]) | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('base', '0003_delete_organizationlogrecord'), | ||
] | ||
|
||
operations = [ | ||
linter.IgnoreMigration(), # adding a unique constraint after fixing duplicates should be fine | ||
migrations.AlterField( | ||
model_name='usernotificationpolicy', | ||
name='order', | ||
field=models.PositiveIntegerField(db_index=True, editable=False, null=True), | ||
), | ||
migrations.RunPython(fix_duplicate_order_user_notification_policy, migrations.RunPython.noop), | ||
migrations.AddConstraint( | ||
model_name='usernotificationpolicy', | ||
constraint=models.UniqueConstraint(fields=('user_id', 'important', 'order'), name='unique_user_notification_policy_order'), | ||
), | ||
] |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Increasing
max_connections
for local concurrency tests that open more connections than default number (151)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what do you think about adding a
./dev/mysql.ini
file?We have four containers (two in both this file +
docker-compose-mysql-rabbitmq.yml
) which run themysql
image. We could de-dupe this config and just volume mount this new file into all of these containers.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All 4 mysql containers share only these two settings:
--character-set-server=utf8mb4
and--collation-server=utf8mb4_unicode_ci
. Not sure if deduping those is worth of the extra complexity added by volume mounting theini
file. I'll take a closer look at this, I think it should be outside of the scope of this PR.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that it's outside the scope of this PR 👍 just a thought that came to mind 😄