Expose "merge request tracking" feature for People data
more to come i'm sure, but this covers the basics
This commit is contained in:
parent
cf32d4235e
commit
ac133ce830
105
tailbone/templates/people/index.mako
Normal file
105
tailbone/templates/people/index.mako
Normal 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()}
|
40
tailbone/templates/people/merge-requests/view.mako
Normal file
40
tailbone/templates/people/merge-requests/view.mako
Normal 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()}
|
|
@ -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'):
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue