Skip to content

Commit

Permalink
[PSQL] Fix the cascade effect of deleting token on ChangeRecord and T…
Browse files Browse the repository at this point in the history
…okenHistory

* [Models] Models are now `ON DELETE SET NULL`
* [Template] Adaptation of filters
* [Test] Add a test that checks that ChangeRecord are kept on token deletion. 

---------

Co-authored-by: Thibault Clérice <[email protected]>
  • Loading branch information
Juliettejns and PonteIneptique authored Mar 14, 2024
1 parent 2ca595d commit 372d217
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 34 deletions.
1 change: 1 addition & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def _set_sqlite_case_insensitive_pragma(dbapi_con, connection_record):
""" This ensures that SQLite is not case-insensitive when using LIKEs"""
if isinstance(dbapi_con, SQLite3Connection):
dbapi_con.execute("PRAGMA case_sensitive_like=ON;")
dbapi_con.execute("PRAGMA foreign_keys=ON;")

app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

Expand Down
12 changes: 8 additions & 4 deletions app/main/filters.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from flask import request, url_for
from json import dumps
import math

import locale
from typing import Optional
from json import dumps

from flask import request, url_for

from . import main
from ..models import WordToken

Expand All @@ -27,7 +29,9 @@ def json(obj):


@main.app_template_filter("get_token_uri")
def get_token_uri(token: WordToken):
def get_token_uri(token: Optional[WordToken]):
if token is None:
return "#"
per_page = request.args.get("per_page", 100, int)
page = int(math.ceil(token.order_id / 100))
return url_for(
Expand Down
21 changes: 14 additions & 7 deletions app/models/corpus.py
Original file line number Diff line number Diff line change
Expand Up @@ -633,7 +633,7 @@ class WordToken(db.Model):
left_context = db.Column(db.String(512))
right_context = db.Column(db.String(512))

_changes = db.relationship("ChangeRecord", cascade="all,delete")
_changes = db.relationship("ChangeRecord")

CONTEXT_LEFT = 3
CONTEXT_RIGHT = 3
Expand Down Expand Up @@ -742,7 +742,8 @@ def edit_form(self, form, corpus, user):
old=self.form,
action_type=TokenHistory.TYPES.Edition,
user_id=user.id,
word_token_id=self.id
word_token_id=self.id,
order_id = self.order_id
))
self.form = form
db.session.add(self)
Expand Down Expand Up @@ -782,7 +783,8 @@ def add_form(self, form, corpus, user):
new=form,
action_type=TokenHistory.TYPES.Addition,
user_id=user.id,
word_token_id=new_token.id
word_token_id=new_token.id,
order_id = new_token.order_id
))

# Update the contexts
Expand All @@ -809,16 +811,19 @@ def del_form(self, corpus, user):
WordToken.order_id > self.order_id
)).update({WordToken.order_id: WordToken.order_id - 1})

# Update
# Record the change
db.session.add(TokenHistory(
corpus=corpus.id,
new="",
old=self.form,
action_type=TokenHistory.TYPES.Deletion,
user_id=user.id,
word_token_id=self.id
#word_token_id=self.id,
order_id = self.order_id
))


# Update the contexts
self.update_context_around(corpus, delete=self.id)

Expand Down Expand Up @@ -1348,11 +1353,12 @@ class TYPES(enum.Enum):

id = db.Column(db.Integer, primary_key=True, autoincrement=True)
corpus = db.Column(db.Integer, db.ForeignKey('corpus.id', ondelete="CASCADE"))
word_token_id = db.Column(db.Integer, db.ForeignKey('word_token.id'))
word_token_id = db.Column(db.Integer, db.ForeignKey('word_token.id', ondelete='SET NULL'), nullable=True)
user_id = db.Column(db.Integer, db.ForeignKey(User.id))
action_type = db.Column(db.Enum(TYPES), nullable=False)
new = db.Column(db.String(100), nullable=True)
old = db.Column(db.String(100), nullable=True)
order_id = db.Column(db.Integer, nullable=True)
created_on = db.Column(db.DateTime, server_default=db.func.now())

user = db.relationship(User, lazy='select')
Expand Down Expand Up @@ -1464,8 +1470,9 @@ def get_like(corpus_id, form, group_by, category="lemma"):
class ChangeRecord(db.Model):
""" A change record keep track of lemma, POS or morph that have been changed for a particular form"""
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
corpus = db.Column(db.Integer, db.ForeignKey('corpus.id'))
word_token_id = db.Column(db.Integer, db.ForeignKey('word_token.id'))
corpus = db.Column(db.Integer, db.ForeignKey('corpus.id', ondelete="CASCADE"))
word_token_id = db.Column(db.Integer, db.ForeignKey('word_token.id', ondelete="SET NULL"), nullable=True,
default=None)
user_id = db.Column(db.Integer, db.ForeignKey(User.id))
form = db.Column(db.String(128))
lemma = db.Column(db.String(128))
Expand Down
19 changes: 13 additions & 6 deletions tests/test_selenium/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,18 @@

def get_chrome():
# This ensures compatibility with nektos/act
if os.path.isfile('/usr/bin/chromium-browser'):
return '/usr/bin/chromium-browser'
elif os.path.isfile('/usr/bin/chromium'):
return '/usr/bin/chromium'
elif os.path.isfile('/usr/bin/chrome'):
if os.path.isfile('/usr/bin/chrome'):
return '/usr/bin/chrome'
elif os.path.isfile('/usr/bin/google-chrome'):
return '/usr/bin/google-chrome'
elif os.path.isfile('/usr/bin/chromium-browser'):
return '/usr/bin/chromium-browser'
elif os.path.isfile('/usr/bin/chromium'):
return '/usr/bin/chromium'
else:
return None



class _element_has_count(object):
""" An expectation for checking that an element has a particular css class.
Expand Down Expand Up @@ -414,6 +413,14 @@ def url_for_with_port(self, *args, **kwargs):
url = url.replace('://localhost/', '://localhost:%d/' % (self.app.config["LIVESERVER_PORT"]))
return url

def token_dropdown_link(self, tok_id, link):
self.driver.get(self.url_for_with_port("main.tokens_correct", corpus_id="1"))
self.driver_find_element_by_id("dd_t" + str(tok_id)).click()
self.driver.implicitly_wait(2)
dd = self.driver_find_element_by_css_selector("*[aria-labelledby='dd_t{}']".format(tok_id))
self.element_find_element_by_partial_link_text(dd, link).click()
self.driver.implicitly_wait(2)


class TokenCorrectBase(TestBase):
""" Base class with helpers to test token edition page """
Expand Down
35 changes: 35 additions & 0 deletions tests/test_selenium/test_token_correct.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from tests.test_selenium.base import TokenCorrectBase, TokenCorrect2CorporaBase
import selenium
from sqlalchemy import text


class TestTokenCorrectWauchierCorpus(TokenCorrectBase):
Expand Down Expand Up @@ -223,3 +224,37 @@ def test_edit_token_lemma_with_typeahead_click(self):
"s", id_row=str(self.first_token_id(2)+1), corpus_id="2",
autocomplete_selector=".autocomplete-suggestion[data-val='saint']"
)

def test_correct_delete(self):
""" [TokenCorrectDelete] Check that we are able to edit then delete the form of a token """
self.addCorpus(with_token=True, with_allowed_lemma=True, tokens_up_to=24)
# We edit the lemma of the first token
token, status_text, row = self.edith_nth_row_value("saint", id_row="1")
# We checked it was saved
self.assert_saved(row)
self.assertEqual(
len(self.db.session.execute(text("SELECT * FROM change_record")).all()), 1,
"There should be one record in the history"
)
# We delete the token
self.token_dropdown_link(1, "Delete")
# Confirm the token's form on delete form
inp = self.driver_find_element_by_css_selector("input[name='form']")
inp.clear()
inp.send_keys("De")
# Delete
self.driver_find_element_by_css_selector("button[type='submit']").click()
# Check that it was removed
row = self.driver_find_elements_by_css_selector("tr.token-anchor[data-token-order='1']")[0]
self.assertEqual(
self.element_find_elements_by_tag_name(row, "td")[1].text, "seint",
"The token has been removed"
)
self.driver_find_element_by_link_text("History").click()
self.assertEqual(
len(self.db.session.execute(text("SELECT * FROM change_record")).all()), 1,
"There should be one record in the history"
)
history_row = self.driver_find_elements_by_css_selector("tbody tr.history")
self.assertEqual(len(history_row), 1, "There should be one record in the history")

25 changes: 8 additions & 17 deletions tests/test_selenium/test_tokens_edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,6 @@ class TestTokenEdit(TestBase):
""" Check token form edition, token update, token delete
"""

def dropdown_link(self, tok_id, link):
self.driver.get(self.url_for_with_port("main.tokens_correct", corpus_id="1"))
self.driver_find_element_by_id("dd_t"+str(tok_id)).click()
self.driver.implicitly_wait(2)
dd = self.driver_find_element_by_css_selector("*[aria-labelledby='dd_t{}']".format(tok_id))
self.element_find_element_by_partial_link_text(dd, link).click()
self.driver.implicitly_wait(2)

def change_form_value(self, value):
inp = self.driver_find_element_by_css_selector("input[name='form']")
inp.clear()
Expand Down Expand Up @@ -46,7 +38,7 @@ def test_edition(self):
""" [TokenEdit] Check that we are able to edit the form of a token """
self.addCorpus("wauchier")
# First edition
self.dropdown_link(5, "Edit")
self.token_dropdown_link(5, "Edit")
self.change_form_value("oulala")
self.driver_find_element_by_css_selector("button[type='submit']").click()
self.driver.implicitly_wait(5)
Expand All @@ -69,7 +61,7 @@ def test_edition(self):
"History should be saved"
)
# Second edition
self.dropdown_link(8, "Edit")
self.token_dropdown_link(8, "Edit")
self.change_form_value("Oulipo")
self.driver_find_element_by_css_selector("button[type='submit']").click()
self.driver.implicitly_wait(5)
Expand All @@ -96,7 +88,7 @@ def test_addition(self):
""" [TokenEdit] Check that we are able to add tokens"""
self.addCorpus("wauchier")
# First edition
self.dropdown_link(5, "Add")
self.token_dropdown_link(5, "Add")
self.change_form_value("oulala")
self.driver_find_element_by_css_selector("button[type='submit']").click()
self.driver.implicitly_wait(5)
Expand All @@ -120,7 +112,7 @@ def test_addition(self):
"History should be saved"
)
# Second edition
self.dropdown_link(8, "Add")
self.token_dropdown_link(8, "Add")
self.change_form_value("Oulipo")
self.driver_find_element_by_css_selector("button[type='submit']").click()
self.driver.implicitly_wait(5)
Expand All @@ -143,16 +135,16 @@ def test_addition(self):
"History should be saved"
)

def test_delete(self):
""" [TokenEdit] Check that we are able to edit the form of a token """
def test_edit_delete(self):
""" [TokenEdit] Check that we are able to edit then delete the form of a token """
self.addCorpus("wauchier")

# Get the original value
self.driver.get(self.url_for_with_port("main.tokens_correct", corpus_id="1"))
original_set = self.select_context_around(5)

# First we add a token
self.dropdown_link(5, "Add")
self.token_dropdown_link(5, "Add")
self.change_form_value("oulala")
self.driver_find_element_by_css_selector("button[type='submit']").click()
self.driver.implicitly_wait(5)
Expand All @@ -177,11 +169,10 @@ def test_delete(self):
)

# Then we remove it
self.dropdown_link(6, "Delete")
self.token_dropdown_link(6, "Delete")
self.change_form_value("oulala")
self.driver_find_element_by_css_selector("button[type='submit']").click()
self.driver.implicitly_wait(5)

self.assertEqual(
self.select_context_around(5),
original_set,
Expand Down

0 comments on commit 372d217

Please sign in to comment.