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

Ability to use fields from related models for defining for conditions #47

Open
tiholic opened this issue Dec 1, 2021 · 3 comments
Open
Labels
documentation Improvements or additions to documentation

Comments

@tiholic
Copy link

tiholic commented Dec 1, 2021

Here's an example representation of my models:

class Tag(models.Model):
    user = models.ForeignKey('auth.User', on_delete=models.CASCADE)

class Activity(models.Model):
    user = models.ForeignKey('auth.User', on_delete=models.CASCADE)
    tags = models.ManyToManyField(Tag, through='TagBinding')

class TagBinding(models.Model):
    tag = models.ForeignKey(Tag)
    activity = models.ForeignKey(Activity)

example picked from this SO Question

And I wanted to make sure tag.user is same as activity.user. So I added the following trigger on TagBinding class

@pgtrigger.register(
    pgtrigger.Protect(
        name='ensure_same_user',
        operation=pgtrigger.Insert | pgtrigger.Update,
        condition=pgtrigger.Q(new__tag__user_id__df=pgtrigger.F('new__activity__user_id'))
        )
    )

Ad when running ./manage.py pgtrigger install, it throws:

django.core.exceptions.FieldDoesNotExist: TagBinding has no field named 'activity__user_id'

Tried dropping the _id from user_id from both F statement and Q statement. No luck.

Is there any way to work around this?

I mainly want to use triggers to ensure that my related data belongs to same user (tried using constraints, but they cannot be configured on related models, so I read that triggers are the solution, but the above errors put a question mark on this approach). Should I do application level validation instead? Please suggest on what can be done here @wesleykendall :)

@MarkKoz
Copy link

MarkKoz commented Mar 22, 2022

Have not tested this, but as a workaround, try a pgtrigger.Trigger with a func and without a condition. The function can execute subqueries to resolve the relationships, and it can use the subquery results in an IF to determine whether an exception should be raised.

@tiholic
Copy link
Author

tiholic commented Apr 12, 2022

Works wonders! Thanks for the suggestions @MarkKoz !

Here's a made-up script for anyone looking at similar requirement:

We are trying to ensure that the City has same Country as that assigned to State. i.e. city.state.country == city.country

class State(models.Model):
    country = models.ForeignKey(Country)
    class Meta:
        db_table = 'myapp_state'

@pgtrigger.register(
   pgtrigger.Trigger(
      name='ensure_same_country',
          when=pgtrigger.Before,
          operation=pgtrigger.Insert | pgtrigger.Update,
          func=f"""
              DECLARE
                  country_id   INTEGER;
              BEGIN
                SELECT t.country_id INTO country_id FROM myapp_state as t WHERE t.id=NEW.country_id LIMIT 1;
                IF country_id != NEW.country_id THEN
                    RAISE EXCEPTION 'invalid country_id: country being inserted is "%" which is not equal to country assigned to strategy_task(%) %', NEW.country_id, NEW.state_id, country_id;
                END IF;
                RETURN NEW;
              END;""",
            )
        )
class City(models.Model):
    country = models.ForeignKey(Country)
    state = models.ForeignKey(State)

@tiholic tiholic closed this as completed Apr 12, 2022
@wesleykendall
Copy link
Member

@tiholic thanks for showing the example! Unfortunately it's not possible to do that type of condition with Postgres trigger conditions. Trigger conditions can only be defined on table columns and cannot do joins.

I'm going to mark this as a wontfix. I'm going re-open this issue for now and close it after I:

  1. Add it to the docs that it's not possible to do conditions on joined data
  2. Add the city/state example to the trigger cookbook for others

@wesleykendall wesleykendall reopened this Aug 9, 2022
@wesleykendall wesleykendall added the documentation Improvements or additions to documentation label Aug 9, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

3 participants