Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
147 commits
Select commit Hold shift + click to select a range
d63ef49
Added mark_incomplete to setup command
jayoshih Jan 15, 2021
6c53039
Disable PWA manifest
micahscopes Jan 16, 2021
b8a111e
Merge pull request #2810 from micahscopes/disable-pwa-manifest
rtibbles Jan 19, 2021
dc0cae3
Actually show no changes to review screen on staging page instead of …
jayoshih Jan 19, 2021
ee746c3
Merge branch 'hotfixes' of https://github.com/learningequality/studio…
jayoshih Jan 19, 2021
450d26d
Merge branch 'master' of https://github.com/learningequality/studio i…
jayoshih Jan 19, 2021
bad6b59
Fixed permission checking on node diff endpoints
jayoshih Jan 19, 2021
b90a34a
Merge pull request #2804 from jayoshih/setup
rtibbles Jan 19, 2021
2b352f1
Added deleted filter to settings channels
jayoshih Jan 19, 2021
1c020a1
Fixed typo in publish modal
jayoshih Jan 19, 2021
158539d
Switched order in which incomplete and uploading node validation mess…
jayoshih Jan 19, 2021
c41af83
Make sure actions on resource panel don't get pushed offscreen
jayoshih Jan 20, 2021
24a32e8
Merge pull request #2837 from jayoshih/settings-channels
jayoshih Jan 20, 2021
453d4f1
Merge pull request #2835 from jayoshih/staging-fixes
jayoshih Jan 20, 2021
a8b3eb8
Don't overwrite extra_fields data on updateContentNode
jayoshih Jan 20, 2021
04988c7
Always show M/N fields on their own row and use labels instead of hints
jayoshih Jan 20, 2021
b272a78
Update devsetup to the latest validation setup changes
MisRob Jan 20, 2021
be58db9
Properly chunk complete=True updating.
rtibbles Jan 20, 2021
e6546bc
Merge pull request #2840 from jayoshih/render-fix
rtibbles Jan 20, 2021
b73cee1
Merge pull request #2808 from learningequality/develop
rtibbles Jan 20, 2021
45566d9
Add PG advisory locking for MPTT tree ops
bjester Jan 20, 2021
433dded
Remove print
bjester Jan 20, 2021
9eb59e3
Fix concurrency test
bjester Jan 21, 2021
1f9ed8d
Add comment
bjester Jan 21, 2021
dd089d4
Remove reference to Pipfile
bjester Jan 21, 2021
f5416cb
Fix arguments
bjester Jan 21, 2021
404eb79
Use empty folder icon in topic tree hierarchy to reflect topics witho…
jayoshih Jan 21, 2021
36cec47
Ensure that dropped connections do not disrupt task execution or sign…
rtibbles Jan 21, 2021
4ba8afd
Use less aggressive connection refreshing to only catch cases where i…
rtibbles Jan 21, 2021
7c9226e
Merge pull request #2841 from jayoshih/mastery-fix
MisRob Jan 21, 2021
1eff469
Rename connection checking function to be more descriptive.
rtibbles Jan 21, 2021
2bdad0e
Add more code comments.
rtibbles Jan 21, 2021
47786ce
Merge pull request #2843 from rtibbles/no_chunking_for_old_men
rtibbles Jan 21, 2021
fe73455
Merge pull request #2848 from rtibbles/interface_closed_for_tasks
rtibbles Jan 21, 2021
d7c96dc
Defensively try to set spawn start method, otherwise skip
bjester Jan 21, 2021
e3ae8e0
Move parent mapping out of lock
bjester Jan 21, 2021
049c75e
Revert complete attribute deletion
MisRob Jan 21, 2021
673b715
Fix syntax
MisRob Jan 21, 2021
eaedcab
Merge pull request #2851 from learningequality/hotfixes
aronasorman Jan 21, 2021
79b257b
add a thumbnail_encoding serializer so extra nested fields can be acc…
marcellamaki Jan 21, 2021
1f29929
Merge pull request #2842 from MisRob/devsetup-complete
rtibbles Jan 21, 2021
f464ddf
remove unnecessary DictField
marcellamaki Jan 21, 2021
747516d
Merge pull request #2852 from marcellamaki/thumbnail-encoding-serializer
rtibbles Jan 21, 2021
fbf38fc
Fix advisory locks not actually locking
bjester Jan 21, 2021
db26e82
Be sure to update your tests at the same time as the code...
bjester Jan 21, 2021
280da39
Add defensive check for node in results
bjester Jan 21, 2021
dce7ffe
Merge pull request #2845 from bjester/hot-locking
rtibbles Jan 21, 2021
5fd66f8
Merge pull request #2854 from bjester/mptt-refresh
bjester Jan 21, 2021
5bd3478
Don't close channel edit modal on "Keep editing" button click
jayoshih Jan 22, 2021
b8ee345
Update code comments
MisRob Jan 18, 2021
db1070f
Self-heal nodes' validation on edit modal load
MisRob Jan 18, 2021
2553435
Improve code readability
MisRob Jan 19, 2021
58fc77c
Make all nodes array unique
MisRob Jan 19, 2021
46ee11e
Mark a test node incorrectly as incomplete
MisRob Jan 21, 2021
ff5a50a
Use resource .get() instead of directly to IndexedDB to defend agains…
bjester Jan 22, 2021
ef508a5
Add newly created channels to the current channel list page
micahscopes Jan 22, 2021
c7b9f26
Merge pull request #2859 from micahscopes/add-new-channel-to-page
micahscopes Jan 22, 2021
b05e336
Add test
bjester Jan 22, 2021
0be98f2
Assert channel and position, fix linting
bjester Jan 22, 2021
124fbb7
Don't paginate starred channel list for more rapid updates
jayoshih Jan 22, 2021
1bcacf5
Set exclude descendants to object
bjester Jan 22, 2021
d1e43ca
Redirect on login if using old url
jayoshih Jan 25, 2021
fdf214d
Merge pull request #2869 from jayoshih/login-redirect
jayoshih Jan 25, 2021
b505664
Convert markdown to HTML and back again
micahscopes Jan 25, 2021
5997824
Merge pull request #2862 from jayoshih/star
micahscopes Jan 25, 2021
8272577
Refactored resource handling, started tests
bjester Jan 26, 2021
695bc43
Merge pull request #2870 from micahscopes/markdown-editor-apply-conve…
micahscopes Jan 26, 2021
e054b5b
Merge pull request #2863 from learningequality/hotfixes
rtibbles Jan 26, 2021
da2e998
Merge pull request #2857 from MisRob/node-validation-self-healing
rtibbles Jan 26, 2021
9d6853b
More tests and fixes
bjester Jan 26, 2021
e9764ad
Return node_id from duplicate task result
bjester Jan 26, 2021
9d52ae5
Fix promise chain
MisRob Jan 27, 2021
bff6956
add a promise to save and update everything in the difftracker before…
marcellamaki Jan 27, 2021
1cf913d
Fix duplicate nodes test
bjester Jan 27, 2021
40587c3
Fixed login case sensitivity
jayoshih Jan 27, 2021
ea1ac03
Don't calculate data when there are not resources in the channel
Jan 27, 2021
2bd1ff2
Added empty lines for better visibility
Jan 27, 2021
ebbcd48
Do stale while invalidate for gets.
rtibbles Jan 27, 2021
fea5dfc
Redirect to the root staging view if the staging root id changes.
rtibbles Jan 27, 2021
a3d4262
Add some login tests
bjester Jan 28, 2021
26fe375
Split out test into separate cases for single and duplicate
bjester Jan 28, 2021
3401a24
Add unique index on lower(email) where active
bjester Jan 28, 2021
c56e002
Review fixes
bjester Jan 28, 2021
7d7a96e
Replace the descendants queryset by a more performant query
Jan 28, 2021
a5ffcf9
Merge pull request #2882 from jredrejo/fix_no_resources
rtibbles Jan 28, 2021
e0bc4aa
Further fixes and testing
bjester Jan 28, 2021
c207187
Merge pull request #2858 from bjester/source-node-import-channel
bjester Jan 28, 2021
9ee0aa4
Merge pull request #2877 from MisRob/fix-logout-promise
bjester Jan 28, 2021
f22b589
Fix accidental key swap
bjester Jan 28, 2021
0dd6073
More tests
bjester Jan 28, 2021
4cfe95c
Merge pull request #2856 from jayoshih/channel-save
rtibbles Jan 28, 2021
1bad503
Merge pull request #2838 from jayoshih/publish-wording
rtibbles Jan 28, 2021
80a3021
Merge pull request #2839 from jayoshih/validation-order
rtibbles Jan 28, 2021
f8e0474
Merge pull request #2847 from jayoshih/topic-icon
rtibbles Jan 28, 2021
4ffc14c
Fix uniqueness on index, add more tests and comments
bjester Jan 28, 2021
2075d17
switching to calling immediate save action using refs
marcellamaki Jan 29, 2021
a7822db
remove leftover references to closingModal and return promise in Edi…
marcellamaki Jan 29, 2021
4834350
Merge pull request #2880 from jayoshih/login
bjester Jan 29, 2021
fb16568
Merge pull request #2879 from marcellamaki/0-incomplete-resources-val…
rtibbles Jan 29, 2021
081d96a
Merge pull request #2884 from rtibbles/stale_while_invalidate_get
rtibbles Jan 29, 2021
c1381ea
Merge pull request #2887 from learningequality/hotfixes
rtibbles Feb 1, 2021
cff218e
Fix sortby issue
bjester Feb 1, 2021
caaadfe
Merge pull request #2893 from bjester/fix-copy-to-clipboard
sairina Feb 1, 2021
407e90a
Merge pull request #2896 from learningequality/hotfixes
aronasorman Feb 2, 2021
29eb095
Commented out code with options for undo and cancel in global snackbar
sairina Feb 2, 2021
d4ed6e1
Move MoveModal out of a parent v-if block
MisRob Jan 29, 2021
5164a6a
Remove unnecessary v-if
MisRob Jan 29, 2021
b1b88d7
Specs for CurrentTopicView
MisRob Jan 31, 2021
fe90cb7
Add a function to reset global object in Jest tests
MisRob Feb 2, 2021
049bd45
add conditions for resource syncing to show up in progress modal
marcellamaki Feb 3, 2021
481a6b5
Refactor invalid get call
bjester Feb 4, 2021
46b95ca
refactor to find current task based on syncing or publishing activity
marcellamaki Feb 4, 2021
4cbb6f3
Add more tests
bjester Feb 4, 2021
74cfc44
Linting
bjester Feb 4, 2021
62741fa
Add some tests to verify endpoint functionality
bjester Feb 4, 2021
cde5fa7
Remove unneeded override
bjester Feb 4, 2021
908efd2
Update response format and test after adding new method
bjester Feb 4, 2021
9bf7bc5
Review update
bjester Feb 4, 2021
22dccd6
Linting
bjester Feb 4, 2021
b829b5f
Merge pull request #2907 from bjester/fix-invalid-get
rtibbles Feb 4, 2021
f042992
Merge pull request #2897 from sairina/hide-popups
rtibbles Feb 4, 2021
8f31de2
add event to handle syncing errors when there is no imported content
marcellamaki Feb 4, 2021
a83b71b
Hide the delete button from Channel Edit page
sairina Feb 5, 2021
6313a98
Do not validate license for topics
MisRob Feb 8, 2021
b190d51
Merge pull request #2915 from MisRob/mark-incomplete-fix
rtibbles Feb 8, 2021
d8fe718
simplify noSyncing work and update tests
marcellamaki Feb 8, 2021
cdfd96e
Add fallback value of ‘0’ when ‘border-left’ and ‘border-top’ strings…
jonboiser Feb 8, 2021
5519e2c
Replace all usages of `parseInt` with `safeParseInt`
jonboiser Feb 8, 2021
4934fbc
Merge pull request #2916 from jonboiser/fix-pdf-on-ff
rtibbles Feb 8, 2021
bb1d8e6
Merge pull request #2910 from sairina/hide-delete
micahscopes Feb 8, 2021
9b28cb0
no syncing should default to percentProgress 100 since the task is 'c…
marcellamaki Feb 8, 2021
c83ee67
Add tests for activate_channel endpoint.
rtibbles Feb 8, 2021
bc8c766
add check current channel condition to isSyncing() and fix whitespace
marcellamaki Feb 9, 2021
ce8dec1
Merge pull request #2909 from marcellamaki/update-progress-modal
rtibbles Feb 9, 2021
e0bbec6
Merge pull request #2918 from rtibbles/no_double_counting
rtibbles Feb 9, 2021
60a8496
Skip async server refresh for move/copy operations
bjester Feb 9, 2021
d169d90
Fix siblings sort before target node search
bjester Feb 9, 2021
7401888
Update test
bjester Feb 9, 2021
525f379
Remove console.log
bjester Feb 9, 2021
8e34c0e
Merge pull request #2901 from MisRob/move-modal-fixes
bjester Feb 9, 2021
80dbcec
Merge pull request #2921 from bjester/move-node-heisenbug
rtibbles Feb 10, 2021
7e6ba6f
Merge pull request #2923 from learningequality/hotfixes
rtibbles Feb 10, 2021
a9dc284
Add guard when channel.accessible_languages is null
jonboiser Feb 10, 2021
2fcca0f
Merge pull request #2929 from jonboiser/guard-null-accessibles_language
rtibbles Feb 10, 2021
baf6bc7
Use request.data instead of request.body to prevent RawPostDataExcept…
rtibbles Feb 11, 2021
d1a9de9
Merge pull request #2931 from rtibbles/activate_channel_body
rtibbles Feb 11, 2021
b8b095a
Merge pull request #2936 from learningequality/hotfixes
rtibbles Feb 11, 2021
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
2 changes: 1 addition & 1 deletion PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Addresses #*PR# HERE*
- [ ] Are there any new ways this uses user data that needs to be factored into our [Privacy Policy](https://github.com/learningequality/studio/tree/master/contentcuration/contentcuration/templates/policies/text)?
- [ ] Are there any new interactions that need to be added to the [QA Sheet](https://docs.google.com/spreadsheets/d/1HF4Gy6rb_BLbZoNkZEWZonKFBqPyVEiQq4Ve6XgIYmQ/edit#gid=0)?
- [ ] Are there opportunities for using Google Analytics here (if applicable)?
- [ ] If the Pipfile has been changed, is the updated Pipfile.lock file also included in this PR?
- [ ] If any Python requirements have changed, are the updated requirements.txt files also included in this PR?
- [ ] Are the migrations [safe for a large db](https://www.braintreepayments.com/blog/safe-operations-for-high-volume-postgresql/) (if applicable)?

## Comments
Expand Down
79 changes: 79 additions & 0 deletions contentcuration/contentcuration/db/advisory_lock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import logging as logger
from contextlib import contextmanager

from django.db import connection


logging = logger.getLogger(__name__)


class AdvisoryLockBusy(RuntimeError):
pass


@contextmanager
def execute_lock(key1, key2=None, unlock=False, session=False, shared=False, wait=True):
"""
Creates or destroys an advisory lock within postgres

:param key1: An int sent to the PG lock function
:param key2: A 2nd int sent to the PG lock function
:param unlock: A bool representing whether query should use `unlock`
:param session: A bool indicating if this should persist outside of transaction
:param shared: A bool indicating if this should be shared, otherwise exclusive
:param wait: A bool indicating if it should use a `try` PG function
"""
if not session:
if not connection.in_atomic_block:
raise NotImplementedError("Advisory lock requires transaction")
if unlock:
raise NotImplementedError("Transaction level locks unlock automatically")

keys = [key1]
if key2 is not None:
keys.append(key2)

query = "SELECT pg{_try}_advisory_{xact_}{lock}{_shared}({keys}) AS lock;".format(
_try="" if wait else "_try",
xact_="" if session else "xact_",
lock="unlock" if unlock else "lock",
_shared="_shared" if shared else "",
keys=", ".join(["%s" for i in range(0, 2 if key2 is not None else 1)])
)

log_query = "'{}' with params {}".format(query, keys)
logging.debug("Acquiring advisory lock: {}".format(query, log_query))
with connection.cursor() as c:
c.execute(query, keys)
logging.debug("Acquired advisory lock: {}".format(query, log_query))
yield c


def advisory_lock(key1, key2=None, shared=False):
"""
Creates a transaction level advisory lock that blocks until ready

:param key1: int
:param key2: int
:param shared: bool
"""
with execute_lock(key1, key2=key2, shared=shared):
# the lock will exist until the transaction is either committed or rolled-back
pass


def try_advisory_lock(key1, key2=None, shared=False):
"""
Creates a transaction level advisory lock that doesn't block

:param key1: int
:param key2: int
:param shared: bool
:raises: AdvisoryLockBusy
"""
with execute_lock(key1, key2=key2, shared=shared, wait=False) as cursor:
# for `try` locks, the PG function will return True or False,
# representing whether the lock was acquired or not
results = cursor.fetchone()
if not results[0]:
raise AdvisoryLockBusy("Unable to acquire advisory lock")
38 changes: 14 additions & 24 deletions contentcuration/contentcuration/db/models/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from mptt.managers import TreeManager
from mptt.signals import node_moved

from contentcuration.db.advisory_lock import advisory_lock
from contentcuration.db.models.query import CustomTreeQuerySet
from contentcuration.utils.tasks import increment_progress
from contentcuration.utils.tasks import set_total
Expand All @@ -32,6 +33,7 @@
# The exact optimum batch size is probably highly dependent on tree
# topology also, so these rudimentary tests are likely insufficient
BATCH_SIZE = 100
TREE_LOCK = 1001


class CustomManager(Manager.from_queryset(CTEQuerySet)):
Expand Down Expand Up @@ -81,55 +83,43 @@ def _get_next_tree_id(self, *args, **kwargs):
return new_id

@contextlib.contextmanager
def _attempt_lock(self, tree_ids, values):
def _attempt_lock(self, tree_ids, shared_tree_ids=None):
"""
Internal method to allow the lock_mptt method to do retries in case of deadlocks
"""
shared_tree_ids = shared_tree_ids or []

start = time.time()
with transaction.atomic():
# Issue a separate lock on each tree_id
# in a predictable order.
# This will mean that every process acquires locks in the same order
# and should help to minimize deadlocks
for tree_id in tree_ids:
execute_queryset_without_results(
self.select_for_update()
.order_by()
.filter(tree_id=tree_id)
.values(*values)
)
advisory_lock(TREE_LOCK, key2=tree_id, shared=tree_id in shared_tree_ids)
yield
log_lock_time_spent(time.time() - start)

@contextlib.contextmanager
def lock_mptt(self, *tree_ids):
def lock_mptt(self, *tree_ids, **kwargs):
tree_ids = sorted((t for t in set(tree_ids) if t is not None))
shared_tree_ids = kwargs.pop('shared_tree_ids', [])
# If this is not inside the context of a delay context manager
# or updates are not disabled set a lock on the tree_ids.
if (
not self.model._mptt_is_tracking
and self.model._mptt_updates_enabled
and tree_ids
):
# Lock based on MPTT columns for updates on any of the tree_ids specified
# until the end of this transaction
mptt_opts = self.model._mptt_meta
values = (
mptt_opts.tree_id_attr,
mptt_opts.left_attr,
mptt_opts.right_attr,
mptt_opts.level_attr,
mptt_opts.parent_attr,
)
try:
with self._attempt_lock(tree_ids, values):
with self._attempt_lock(tree_ids, shared_tree_ids=shared_tree_ids):
yield
except OperationalError as e:
if "deadlock detected" in e.args[0]:
logging.error(
"Deadlock detected while trying to lock ContentNode trees for mptt operations, retrying"
)
with self._attempt_lock(tree_ids, values):
with self._attempt_lock(tree_ids, shared_tree_ids=shared_tree_ids):
yield
else:
raise
Expand Down Expand Up @@ -180,7 +170,7 @@ def _mptt_refresh(self, *nodes):
}
for node in nodes:
# Set the values on each of the nodes
if node.id:
if node.id and node.id in values_lookup:
values = values_lookup[node.id]
for k, v in values.items():
setattr(node, k, v)
Expand Down Expand Up @@ -556,11 +546,11 @@ def _deep_copy(
excluded_descendants,
can_edit_source_channel,
):

nodes_to_copy = self._all_nodes_to_copy(node, excluded_descendants)
# lock mptt source tree with shared advisory lock
with self.lock_mptt(node.tree_id, shared_tree_ids=[node.tree_id]):
nodes_to_copy = list(self._all_nodes_to_copy(node, excluded_descendants))

nodes_by_parent = {}

for copy_node in nodes_to_copy:
if copy_node.parent_id not in nodes_by_parent:
nodes_by_parent[copy_node.parent_id] = []
Expand Down
20 changes: 10 additions & 10 deletions contentcuration/contentcuration/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,35 +89,35 @@ def save(self, request=None, extra_email_context=None, **kwargs):
user.
"""
email = self.cleaned_data["email"]
inactive_user = User.objects.filter(email=email, is_active=False).first()
user = User.get_for_email(email)

if inactive_user:
if user and user.is_active:
super(ForgotPasswordForm, self).save(request=request, extra_email_context=extra_email_context, **kwargs)
elif user:
# For users who were invited but hadn't registered yet
if not inactive_user.password:
if not user.password:
context = {
'site': extra_email_context.get('site'),
'user': inactive_user,
'user': user,
'domain': extra_email_context.get('domain'),
}
subject = render_to_string('registration/password_reset_subject.txt', context)
subject = ''.join(subject.splitlines())
message = render_to_string('registration/registration_needed_email.txt', context)
inactive_user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL, )
user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL, )
else:
activation_key = self.get_activation_key(inactive_user)
activation_key = self.get_activation_key(user)
context = {
'activation_key': activation_key,
'expiration_days': settings.ACCOUNT_ACTIVATION_DAYS,
'site': extra_email_context.get('site'),
'user': inactive_user,
'user': user,
'domain': extra_email_context.get('domain'),
}
subject = render_to_string('registration/password_reset_subject.txt', context)
subject = ''.join(subject.splitlines())
message = render_to_string('registration/activation_needed_email.txt', context)
inactive_user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL, )
else:
super(ForgotPasswordForm, self).save(request=request, extra_email_context=extra_email_context, **kwargs)
user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL, )

def get_activation_key(self, user):
"""
Expand Down
Loading