mergeconflict.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. from rest_framework.exceptions import ValidationError
  2. from django.utils.translation import ugettext as _
  3. from misago.threads.models import Poll
  4. class MergeConflictHandler(object):
  5. def __init__(self, threads):
  6. self.items = []
  7. self.choices = {0: None}
  8. self._is_valid = False
  9. self._resolution = None
  10. self.threads = threads
  11. self.populate_from_threads(threads)
  12. if len(self.items) == 1:
  13. self._is_valid = True
  14. self._resolution = self.items[0]
  15. def populate_from_threads(self, threads):
  16. raise NotImplementedError('merge handler must define populate_from_threads')
  17. def is_merge_conflict(self):
  18. return len(self.items) > 1
  19. def set_resolution(self, resolution):
  20. try:
  21. resolution_clean = int(resolution)
  22. except (TypeError, ValueError):
  23. return
  24. if resolution_clean in self.choices:
  25. self._resolution = self.choices[resolution_clean]
  26. self._is_valid = True
  27. def is_valid(self):
  28. return self._is_valid
  29. def get_resolution(self):
  30. return self._resolution or None
  31. class BestAnswerMergeHandler(MergeConflictHandler):
  32. data_name = 'best_answer'
  33. def populate_from_threads(self, threads):
  34. for thread in threads:
  35. if thread.has_best_answer:
  36. self.items.append(thread)
  37. self.choices[thread.pk] = thread
  38. self.items.sort(key=lambda thread: (thread.title, thread.id))
  39. def get_available_resolutions(self):
  40. resolutions = [[0, _("Unmark all best answers")]]
  41. for thread in self.items:
  42. resolutions.append([thread.pk, thread.title])
  43. return resolutions
  44. class PollMergeHandler(MergeConflictHandler):
  45. data_name = 'poll'
  46. def populate_from_threads(self, threads):
  47. for thread in threads:
  48. try:
  49. self.items.append(thread.poll)
  50. self.choices[thread.poll.id] = thread.poll
  51. except Poll.DoesNotExist:
  52. pass
  53. self.items.sort(key=lambda poll: poll.question)
  54. def get_available_resolutions(self):
  55. resolutions = [[0, _("Delete all polls")]]
  56. for poll in self.items:
  57. resolutions.append([poll.id, '%s (%s)' % (poll.question, poll.thread.title)])
  58. return resolutions
  59. class MergeConflict(object):
  60. """
  61. Utility class single point of entry for detecting merge conflicts on different properties
  62. and validating user resolutions.
  63. """
  64. HANDLERS = (
  65. BestAnswerMergeHandler,
  66. PollMergeHandler,
  67. )
  68. def __init__(self, data=None, threads=None):
  69. self.data = data or {}
  70. self._handlers = [Handler(threads) for Handler in self.HANDLERS]
  71. self._conflicts = [i for i in self._handlers if i.is_merge_conflict()]
  72. self.set_resolution(data)
  73. def is_merge_conflict(self):
  74. return bool(self._conflicts)
  75. def get_conflicting_fields(self):
  76. return [i.data_name for i in self._conflicts]
  77. def set_resolution(self, data):
  78. for handler in self._conflicts:
  79. data = self.data.get(handler.data_name)
  80. handler.set_resolution(data)
  81. def is_valid(self, raise_exception=False):
  82. if raise_exception:
  83. self.raise_exception()
  84. return all([i.is_valid() for i in self._handlers])
  85. def raise_exception(self):
  86. # if any choice was made by user, we are in validation stage of resolution
  87. for conflict in self._conflicts:
  88. if self.data.get(conflict.data_name) is not None:
  89. self.raise_validation_exception()
  90. break
  91. else:
  92. self.raise_resolutions_exception()
  93. def raise_validation_exception(self):
  94. errors = {}
  95. for conflict in self._conflicts:
  96. if not conflict.is_valid() or self.data.get(conflict.data_name) is None:
  97. errors[conflict.data_name] = [_("Invalid choice.")]
  98. if errors:
  99. raise ValidationError(errors)
  100. def raise_resolutions_exception(self):
  101. resolutions = {}
  102. for conflict in self._conflicts:
  103. key = '%ss' % conflict.data_name
  104. resolutions[key] = conflict.get_available_resolutions()
  105. if resolutions:
  106. raise ValidationError(resolutions)
  107. def get_resolution(self):
  108. resolved_handlers = [i for i in self._handlers if i.is_valid()]
  109. return {i.data_name: i.get_resolution() for i in resolved_handlers}