Expose "merge request tracking" feature for People data

more to come i'm sure, but this covers the basics
This commit is contained in:
Lance Edgar 2021-08-19 18:00:51 -05:00
parent cf32d4235e
commit ac133ce830
4 changed files with 279 additions and 3 deletions

View file

@ -0,0 +1,105 @@
## -*- coding: utf-8; -*-
<%inherit file="/master/index.mako" />
<%def name="grid_tools()">
% if master.mergeable and master.has_perm('request_merge'):
% if use_buefy:
<b-button @click="showMergeRequest()"
icon-pack="fas"
icon-left="object-ungroup"
:disabled="checkedRows.length != 2">
Request Merge
</b-button>
<b-modal has-modal-card
:active.sync="mergeRequestShowDialog">
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Request Merge of 2 People</p>
</header>
<section class="modal-card-body">
<b-table :data="mergeRequestRows"
striped hoverable>
<template slot-scope="props">
<b-table-column field="customer_number"
label="Customer #">
<span v-html="props.row.customer_number"></span>
</b-table-column>
<b-table-column field="first_name"
label="First Name">
<span v-html="props.row.first_name"></span>
</b-table-column>
<b-table-column field="last_name"
label="Last Name">
<span v-html="props.row.last_name"></span>
</b-table-column>
</template>
</b-table>
</section>
<footer class="modal-card-foot">
<b-button @click="mergeRequestShowDialog = false">
Cancel
</b-button>
${h.form(url('{}.request_merge'.format(route_prefix)), **{'@submit': 'submitMergeRequest'})}
${h.csrf_token(request)}
${h.hidden('removing_uuid', **{':value': 'mergeRequestRemovingUUID'})}
${h.hidden('keeping_uuid', **{':value': 'mergeRequestKeepingUUID'})}
<b-button type="is-primary"
native-type="submit"
:disabled="mergeRequestSubmitting">
{{ mergeRequestSubmitText }}
</b-button>
${h.end_form()}
</footer>
</div>
</b-modal>
% endif
% endif
${parent.grid_tools()}
</%def>
<%def name="modify_this_page_vars()">
${parent.modify_this_page_vars()}
<script type="text/javascript">
% if master.mergeable and master.has_perm('request_merge'):
${grid.component_studly}Data.mergeRequestShowDialog = false
${grid.component_studly}Data.mergeRequestRows = []
${grid.component_studly}Data.mergeRequestSubmitText = "Submit Merge Request"
${grid.component_studly}Data.mergeRequestSubmitting = false
${grid.component_studly}.computed.mergeRequestRemovingUUID = function() {
if (this.mergeRequestRows.length) {
return this.mergeRequestRows[0].uuid
}
return null
}
${grid.component_studly}.computed.mergeRequestKeepingUUID = function() {
if (this.mergeRequestRows.length) {
return this.mergeRequestRows[1].uuid
}
return null
}
${grid.component_studly}.methods.showMergeRequest = function() {
this.mergeRequestRows = this.checkedRows
this.mergeRequestShowDialog = true
}
${grid.component_studly}.methods.submitMergeRequest = function() {
this.mergeRequestSubmitting = true
this.mergeRequestSubmitText = "Working, please wait..."
}
% endif
</script>
</%def>
${parent.body()}

View file

@ -0,0 +1,40 @@
## -*- coding: utf-8; -*-
<%inherit file="/master/view.mako" />
<%def name="page_content()">
${parent.page_content()}
% if not instance.merged and request.has_perm('people.merge'):
% if use_buefy:
${h.form(url('people.merge'), **{'@submit': 'submitMergeForm'})}
${h.csrf_token(request)}
${h.hidden('uuids', value=','.join([instance.removing_uuid, instance.keeping_uuid]))}
<b-button type="is-primary"
native-type="submit"
:disabled="mergeFormSubmitting"
icon-pack="fas"
icon-left="object-ungroup">
{{ mergeFormButtonText }}
</b-button>
${h.end_form()}
% endif
% endif
</%def>
<%def name="modify_this_page_vars()">
${parent.modify_this_page_vars()}
% if not instance.merged and request.has_perm('people.merge'):
<script type="text/javascript">
ThisPageData.mergeFormButtonText = "Perform Merge"
ThisPageData.mergeFormSubmitting = false
ThisPage.methods.submitMergeForm = function() {
this.mergeFormButtonText = "Working, please wait..."
this.mergeFormSubmitting = true
}
</script>
% endif
</%def>
${parent.body()}

View file

@ -378,7 +378,7 @@ class MasterView(View):
Return a dictionary of kwargs to be passed to the factory when creating
new grid instances.
"""
checkboxes = self.checkboxes
checkboxes = kwargs.get('checkboxes', self.checkboxes)
if not checkboxes and self.mergeable and self.has_perm('merge'):
checkboxes = True
if not checkboxes and self.supports_set_enabled_toggle and self.has_perm('enable_disable_set'):

View file

@ -33,7 +33,7 @@ import sqlalchemy as sa
from sqlalchemy import orm
from rattail.db import model, api
from rattail.time import localtime
from rattail.time import localtime, make_utc
from rattail.util import OrderedDict
import colander
@ -68,6 +68,7 @@ class PersonView(MasterView):
'last_name',
'phone',
'email',
'merge_requested',
]
form_fields = [
@ -93,6 +94,15 @@ class PersonView(MasterView):
app = self.get_rattail_app()
self.handler = app.get_people_handler()
def make_grid_kwargs(self, **kwargs):
kwargs = super(PersonView, self).make_grid_kwargs(**kwargs)
# turn on checkboxes if user can create a merge reqeust
if self.mergeable and self.has_perm('request_merge'):
kwargs['checkboxes'] = True
return kwargs
def configure_grid(self, g):
super(PersonView, self).configure_grid(g)
@ -123,6 +133,9 @@ class PersonView(MasterView):
g.sorters['email'] = lambda q, d: q.order_by(getattr(model.PersonEmailAddress.address, d)())
g.sorters['phone'] = lambda q, d: q.order_by(getattr(model.PersonPhoneNumber.number, d)())
g.set_label('merge_requested', "MR")
g.set_renderer('merge_requested', self.render_merge_requested)
g.set_sort_defaults('display_name')
g.set_label('display_name', "Full Name")
@ -134,6 +147,23 @@ class PersonView(MasterView):
g.set_link('first_name')
g.set_link('last_name')
def render_merge_requested(self, person, field):
model = self.model
merge_request = self.Session.query(model.MergePeopleRequest)\
.filter(sa.or_(
model.MergePeopleRequest.removing_uuid == person.uuid,
model.MergePeopleRequest.keeping_uuid == person.uuid))\
.filter(model.MergePeopleRequest.merged == None)\
.first()
if merge_request:
use_buefy = self.get_use_buefy()
if use_buefy:
return HTML.tag('span',
class_='has-text-danger has-text-weight-bold',
title="A merge has been requested for this person.",
c="MR")
return "MR"
def get_instance(self):
# TODO: I don't recall why this fallback check for a vendor contact
# exists here, but leaving it intact for now.
@ -383,7 +413,7 @@ class PersonView(MasterView):
raise Exception(reason)
def merge_objects(self, removing, keeping):
self.handler.perform_merge(removing, keeping)
self.handler.perform_merge(removing, keeping, user=self.request.user)
def view_profile(self):
"""
@ -696,6 +726,18 @@ class PersonView(MasterView):
self.request.session.flash("User has been created: {}".format(user.username))
return self.redirect(self.request.route_url('users.view', uuid=user.uuid))
def request_merge(self):
"""
Create a new merge request for the given 2 people.
"""
merge = self.model.MergePeopleRequest()
merge.removing_uuid = self.request.POST['removing_uuid']
merge.keeping_uuid = self.request.POST['keeping_uuid']
merge.requested_by = self.request.user
merge.requested = make_utc()
self.Session.add(merge)
return self.redirect(self.get_index_url())
@classmethod
def defaults(cls, config):
cls._people_defaults(config)
@ -709,6 +751,7 @@ class PersonView(MasterView):
instance_url_prefix = cls.get_instance_url_prefix()
model_key = cls.get_model_key()
model_title = cls.get_model_title()
model_title_plural = cls.get_model_title_plural()
# "profile" perms
# TODO: should let view class (or config) determine which of these are available
@ -777,6 +820,15 @@ class PersonView(MasterView):
config.add_view(cls, attr='make_user', route_name='{}.make_user'.format(route_prefix),
permission='users.create')
# merge requests
if cls.mergeable:
config.add_tailbone_permission(permission_prefix, '{}.request_merge'.format(permission_prefix),
"Request merge for 2 {}".format(model_title_plural))
config.add_route('{}.request_merge'.format(route_prefix), '{}/request-merge'.format(url_prefix),
request_method='POST')
config.add_view(cls, attr='request_merge', route_name='{}.request_merge'.format(route_prefix),
permission='{}.request_merge'.format(permission_prefix))
# TODO: deprecate / remove this
PeopleView = PersonView
@ -888,6 +940,84 @@ class NoteSchema(colander.Schema):
note_text = colander.SchemaNode(colander.String(), missing='')
class MergePeopleRequestView(MasterView):
"""
Master view for the MergePeopleRequest class.
"""
model_class = model.MergePeopleRequest
route_prefix = 'people_merge_requests'
url_prefix = '/people/merge-requests'
creatable = False
editable = False
labels = {
'removing_uuid': "Removing",
'keeping_uuid': "Keeping",
}
grid_columns = [
'removing_uuid',
'keeping_uuid',
'requested',
'requested_by',
'merged',
'merged_by',
]
form_fields = [
'removing_uuid',
'keeping_uuid',
'requested',
'requested_by',
'merged',
'merged_by',
]
def configure_grid(self, g):
super(MergePeopleRequestView, self).configure_grid(g)
g.set_renderer('removing_uuid', self.render_referenced_person_name)
g.set_renderer('keeping_uuid', self.render_referenced_person_name)
g.filters['merged'].default_active = True
g.filters['merged'].default_verb = 'is_null'
g.set_sort_defaults('requested', 'desc')
g.set_link('removing_uuid')
g.set_link('keeping_uuid')
def render_referenced_person_name(self, merge_request, field):
uuid = getattr(merge_request, field)
person = self.Session.query(self.model.Person).get(uuid)
if person:
return six.text_type(person)
return "(person not found)"
def get_instance_title(self, merge_request):
model = self.model
removing = self.Session.query(model.Person).get(merge_request.removing_uuid)
keeping = self.Session.query(model.Person).get(merge_request.keeping_uuid)
return "{} -> {}".format(
removing or "(not found)",
keeping or "(not found)")
def configure_form(self, f):
super(MergePeopleRequestView, self).configure_form(f)
f.set_renderer('removing_uuid', self.render_referenced_person)
f.set_renderer('keeping_uuid', self.render_referenced_person)
def render_referenced_person(self, merge_request, field):
uuid = getattr(merge_request, field)
person = self.Session.query(self.model.Person).get(uuid)
if person:
text = six.text_type(person)
url = self.request.route_url('people.view', uuid=person.uuid)
return tags.link_to(text, url)
return "(person not found)"
def includeme(config):
# autocomplete
@ -900,3 +1030,4 @@ def includeme(config):
PersonView.defaults(config)
PersonNoteView.defaults(config)
MergePeopleRequestView.defaults(config)