diff --git a/tailbone/views/master.py b/tailbone/views/master.py index 0da39c0b..0dfcf149 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -71,6 +71,7 @@ class MasterView(View): editable = True deletable = True bulk_deletable = False + populatable = False mergeable = False downloadable = False cloneable = False @@ -279,8 +280,74 @@ class MasterView(View): form.save() def redirect_after_create(self, instance, mobile=False): + if self.populatable and self.should_populate(instance): + return self.redirect(self.get_action_url('populate', instance, mobile=mobile)) return self.redirect(self.get_action_url('view', instance, mobile=mobile)) + def should_populate(self, obj): + return True + + def populate(self): + """ + View for populating a new object. What exactly this means / does will + depend on the logic in :meth:`populate_object()`. + """ + obj = self.get_instance() + route_prefix = self.get_route_prefix() + permission_prefix = self.get_permission_prefix() + + # showing progress requires a separate thread; start that first + key = '{}.populate'.format(route_prefix) + progress = SessionProgress(self.request, key) + thread = Thread(target=self.populate_thread, args=(obj.uuid, progress)) # TODO: uuid? + thread.start() + + # Send user to progress page. + kwargs = { + 'cancel_url': self.get_action_url('view', obj), + 'cancel_msg': "{} population was canceled.".format(self.get_model_title()), + } + + return self.render_progress(progress, kwargs) + + def populate_thread(self, uuid, progress): # TODO: uuid? + """ + Thread target for populating new object with progress indicator. + """ + # mustn't use tailbone web session here + session = RattailSession() + obj = session.query(self.model_class).get(uuid) + try: + self.populate_object(session, obj, progress=progress) + except Exception as error: + session.rollback() + msg = "{} population failed".format(self.get_model_title()) + log.warning("{}: {}".format(msg, obj), exc_info=True) + session.close() + if progress: + progress.session.load() + progress.session['error'] = True + progress.session['error_msg'] = "{}: {} {}".format(msg, error.__class__.__name__, error) + progress.session.save() + return + + session.commit() + session.refresh(obj) + session.close() + + # finalize progress + if progress: + progress.session.load() + progress.session['complete'] = True + progress.session['success_url'] = self.get_action_url('view', obj) + progress.session.save() + + def populate_object(self, session, obj, progress=None): + """ + You must define this if new objects require population. + """ + raise NotImplementedError + def view(self, instance=None): """ View for viewing details of an existing model record. @@ -1862,6 +1929,12 @@ class MasterView(View): config.add_view(cls, attr='mobile_create', route_name='mobile.{}.create'.format(route_prefix), permission='{}.create'.format(permission_prefix)) + # populate new object + if cls.populatable: + config.add_route('{}.populate'.format(route_prefix), '{}/{{uuid}}/populate'.format(url_prefix)) + config.add_view(cls, attr='populate', route_name='{}.populate'.format(route_prefix), + permission='{}.create'.format(permission_prefix)) + # bulk delete if cls.bulk_deletable: config.add_route('{}.bulk_delete'.format(route_prefix), '{}/bulk-delete'.format(url_prefix))