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
|
Return a dictionary of kwargs to be passed to the factory when creating
|
||||||
new grid instances.
|
new grid instances.
|
||||||
"""
|
"""
|
||||||
checkboxes = self.checkboxes
|
checkboxes = kwargs.get('checkboxes', self.checkboxes)
|
||||||
if not checkboxes and self.mergeable and self.has_perm('merge'):
|
if not checkboxes and self.mergeable and self.has_perm('merge'):
|
||||||
checkboxes = True
|
checkboxes = True
|
||||||
if not checkboxes and self.supports_set_enabled_toggle and self.has_perm('enable_disable_set'):
|
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 sqlalchemy import orm
|
||||||
|
|
||||||
from rattail.db import model, api
|
from rattail.db import model, api
|
||||||
from rattail.time import localtime
|
from rattail.time import localtime, make_utc
|
||||||
from rattail.util import OrderedDict
|
from rattail.util import OrderedDict
|
||||||
|
|
||||||
import colander
|
import colander
|
||||||
|
@ -68,6 +68,7 @@ class PersonView(MasterView):
|
||||||
'last_name',
|
'last_name',
|
||||||
'phone',
|
'phone',
|
||||||
'email',
|
'email',
|
||||||
|
'merge_requested',
|
||||||
]
|
]
|
||||||
|
|
||||||
form_fields = [
|
form_fields = [
|
||||||
|
@ -93,6 +94,15 @@ class PersonView(MasterView):
|
||||||
app = self.get_rattail_app()
|
app = self.get_rattail_app()
|
||||||
self.handler = app.get_people_handler()
|
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):
|
def configure_grid(self, g):
|
||||||
super(PersonView, self).configure_grid(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['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.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_sort_defaults('display_name')
|
||||||
|
|
||||||
g.set_label('display_name', "Full Name")
|
g.set_label('display_name', "Full Name")
|
||||||
|
@ -134,6 +147,23 @@ class PersonView(MasterView):
|
||||||
g.set_link('first_name')
|
g.set_link('first_name')
|
||||||
g.set_link('last_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):
|
def get_instance(self):
|
||||||
# TODO: I don't recall why this fallback check for a vendor contact
|
# TODO: I don't recall why this fallback check for a vendor contact
|
||||||
# exists here, but leaving it intact for now.
|
# exists here, but leaving it intact for now.
|
||||||
|
@ -383,7 +413,7 @@ class PersonView(MasterView):
|
||||||
raise Exception(reason)
|
raise Exception(reason)
|
||||||
|
|
||||||
def merge_objects(self, removing, keeping):
|
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):
|
def view_profile(self):
|
||||||
"""
|
"""
|
||||||
|
@ -696,6 +726,18 @@ class PersonView(MasterView):
|
||||||
self.request.session.flash("User has been created: {}".format(user.username))
|
self.request.session.flash("User has been created: {}".format(user.username))
|
||||||
return self.redirect(self.request.route_url('users.view', uuid=user.uuid))
|
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
|
@classmethod
|
||||||
def defaults(cls, config):
|
def defaults(cls, config):
|
||||||
cls._people_defaults(config)
|
cls._people_defaults(config)
|
||||||
|
@ -709,6 +751,7 @@ class PersonView(MasterView):
|
||||||
instance_url_prefix = cls.get_instance_url_prefix()
|
instance_url_prefix = cls.get_instance_url_prefix()
|
||||||
model_key = cls.get_model_key()
|
model_key = cls.get_model_key()
|
||||||
model_title = cls.get_model_title()
|
model_title = cls.get_model_title()
|
||||||
|
model_title_plural = cls.get_model_title_plural()
|
||||||
|
|
||||||
# "profile" perms
|
# "profile" perms
|
||||||
# TODO: should let view class (or config) determine which of these are available
|
# 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),
|
config.add_view(cls, attr='make_user', route_name='{}.make_user'.format(route_prefix),
|
||||||
permission='users.create')
|
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
|
# TODO: deprecate / remove this
|
||||||
PeopleView = PersonView
|
PeopleView = PersonView
|
||||||
|
|
||||||
|
@ -888,6 +940,84 @@ class NoteSchema(colander.Schema):
|
||||||
note_text = colander.SchemaNode(colander.String(), missing='')
|
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):
|
def includeme(config):
|
||||||
|
|
||||||
# autocomplete
|
# autocomplete
|
||||||
|
@ -900,3 +1030,4 @@ def includeme(config):
|
||||||
|
|
||||||
PersonView.defaults(config)
|
PersonView.defaults(config)
|
||||||
PersonNoteView.defaults(config)
|
PersonNoteView.defaults(config)
|
||||||
|
MergePeopleRequestView.defaults(config)
|
||||||
|
|
Loading…
Reference in a new issue