class UrnConfig(DocumentSchema): """ A document schema representing a WFItem urn_config used to identify unique groups of items """ field = schema.StringProperty() subfield = schema.StringProperty()
class Field(schema.DocumentSchema): """ A document schema representing a WFItem field """ indicator1 = schema.StringProperty() indicator2 = schema.StringProperty() subfields = schema.SchemaDictProperty(SubField()) def __repr__(self): return "<Field i:[%s, %s]>" % (self.indicator1, self.indicator2)
class Penalty(DocumentSchema): """ A document schema representing a User Penalty """ id = schema.IntegerProperty() type = schema.StringProperty(choices=('fault', 'suspend')) start = schema.DateProperty() end = schema.DateProperty() cause = schema.StringProperty()
class BaseField(DocumentSchema): """ A document schema representing a WFItem base field used by field and subfield """ id = schema.StringProperty() field_name = schema.StringProperty() repeat = schema.BooleanProperty() type = schema.StringProperty() default_value = schema.StringProperty() exec_value = schema.ListProperty()
class CirculationLog(Document): _db = get_db("couchflow") type = schema.StringProperty(choices=['loan', 'return', 'renew']) loan_type = schema.StringProperty(choices=['room', 'home', 'interbiblio']) item_type = schema.StringProperty() date = schema.DateProperty(default=datetime.date.today) length = schema.IntegerProperty() item_id = schema.StringProperty() user_id = schema.StringProperty() timestamp_added = schema.DateTimeProperty(default=datetime.datetime.now)
class Loan(DocumentSchema): """ A document schema representing a WFItem loan """ user_id = schema.StringProperty() type = schema.StringProperty(choices=['room', 'home']) start = schema.DateProperty() end = schema.DateProperty() renew_count = schema.IntegerProperty() def __repr__(self): class_name = self.__class__.__name__ start, end = self.start, self.end return '<%s: start:"%s" end:"%s">' % (class_name, start, end)
class Conector(Document): _db = get_db("couchflow") workflow_id = schema.StringProperty() conector_type = schema.BooleanProperty(default=True) name = schema.StringProperty(default="Conector") status = schema.StringProperty() step = schema.IntegerProperty(default=0) is_clone = schema.BooleanProperty(default=False) start = schema.BooleanProperty(default=True) end = schema.BooleanProperty(default=False) active = schema.BooleanProperty() previous_tasks = schema.ListProperty() next_tasks = schema.ListProperty() get_id = property(lambda self: self['_id']) wfitems_ids = schema.ListProperty() def put_step(self): if not self.previous_tasks: self.step = 1 self.save() workflow = WorkFlow.get(self.workflow_id) workflow.steps = self.step return True prev_task = get_task(self.previous_tasks[0]) self.step = prev_task.step + 1 workflow = WorkFlow.get(self.workflow_id) workflow.steps = self.step workflow.save() self.save() return True def back_to_top(self): previous_tasks = [] if self.previous_tasks: for prev_task_id in self.previous_tasks: prev_task = Task.get(prev_task_id) previous_tasks.append(prev_task) return previous_tasks return None def go_deep(self): next_tasks = [] if self.next_tasks: for next_task_id in self.next_tasks: next_task = get_task(next_task_id) next_tasks.append(next_task) return next_tasks return None
class DecisionTask(Task): sentence = schema.StringProperty() confirmation = schema.BooleanProperty(default=None) def check_data(self): return self.confirmation def active_conector(self): if self.conector_id: conector = get_conector(self.conector_id) if conector.doc_type == "SequenceConector" and \ self.confirmation: self.finish_task("finished") self.save() conector.to_next_tasks() return True if conector.doc_type == "SequenceConector" and \ not self.confirmation: self.finish_task("cancel") self.save() return True elif conector.doc_type != "SequenceConector": self.finish_task("finished") self.save() conector.to_next_tasks() return True return True
class Group(Document): _db = get_db("couchauth") name = schema.StringProperty() users = schema.ListProperty() max_loan_days = schema.IntegerProperty() get_id = property(lambda self: self['_id']) def add_user(self, user_id): if user_id not in self.users: self.users.append(user_id) self.save() return True def del_user(self, user_id): if user_id in self.users: del (self.users[self.users.index(user_id)]) self.save() return True def update_users(self, users): for user_id in self.users: if user_id not in users: user = User.get(user_id) user.del_group(self._id) for user_id in users: user = User.get(user_id) user.add_group(self._id) def id(self): return self._id
class UntilConector(Conector): back_task_id = schema.StringProperty(default=None) def add_next_task(self, task): if len(self.next_tasks) >= 2: return False elif len(self.next_tasks) == 1 and \ self.back_task_id == self.next_tasks[0]: self.next_tasks = [task._id] + self.next_tasks self.save() elif len(self.next_tasks) == 1 and \ not self.back_task_id: self.back_task_id = task._id self.next_tasks.append(task._id) elif len(self.next_tasks) == 0: self.next_tasks.append(task._id) if not task.prev_conector_id: task.prev_conector_id = self._id task.save() else: task.have_until = True task.save() self.save() return True def add_previous_task(self, task): if len(self.previous_tasks) > 0: return False self.previous_tasks.append(task._id) return True def check_previous_tasks(self): """ """ def to_next_tasks(self): if len(self.next_tasks) > 1 and self.previous_tasks: previous_tasks = get_task(self.previous_tasks[0]) if previous_tasks.check_data(): task = get_task(self.next_tasks[0]) else: task = get_task(self.next_tasks[1]) task.status = None self.active = True task.active = True task.enabled = True task.exec_process() task.save() previous_tasks.active = False previous_tasks.save() return True return False
class WorkFlow(Document): _db = get_db("couchflow") name = schema.StringProperty() workflow_type = schema.StringProperty() item_type = schema.StringProperty() user_id = schema.StringProperty() conectors = schema.DictProperty() nro_pedido = schema.IntegerProperty(default=0) tasks = schema.DictProperty() merge_conectors = schema.DictProperty() original_task_ids = schema.DictProperty() enabled = schema.BooleanProperty(default=False) steps = schema.IntegerProperty(default=0) is_clone = schema.BooleanProperty(default=False) get_id = property(lambda self: self['_id']) # text shown in the task description = schema.StringProperty() path = schema.ListProperty(schema.StringProperty()) # keep track of starting point of workflow # usefull to get all workflows that starts # with X task type, for example FilterItems first_task_type = schema.StringProperty() first_task_id = schema.StringProperty() visible = schema.BooleanProperty(default=True) def get_item(self): if not self.item_type: return False items = WFItem.view("couchflow/item_names", limit=1, include_docs=True) item = items[self.item_type] item = item.one() return item def get_items(self): item_query = WFItem.view("couchflow/items", include_docs=True) items = {} for item in item_query.all(): items[item.item_type] = (item.name, False) if self.item_type in items: items[self.item_type] = (items[self.item_type][0], True) items = [(key, items[key][0], items[key][1]) for key in items] return items def remove_relations(self): conectors = WorkFlow.view("couchflow/flowconector", include_docs=True) conectors = conectors[self._id] # TODO: bulk delete for conector in conectors: conector.delete() tasks = Task.view("couchflow/flowtask", include_docs=True) tasks = tasks[self._id] for task in tasks: task.delete() return True def set_all_inactive(self): conectors = WorkFlow.view("couchflow/flowconector", include_docs=True) conectors = conectors[self._id] # TODO: bulk save for conector in conectors: conector.active = False conector.save() tasks = Task.view("couchflow/flowtask", include_docs=True) tasks = tasks[self._id] for task in tasks: task.finish_task(None) task.save() return True def get_docs(self): documents = WorkFlow.view("couchflow/flowdocs", include_docs=True) documents = documents[self._id] documents = documents.all() documents.reverse() return documents def set_enabled(self): tasks = Task.view("couchflow/flowtask", include_docs=True) tasks = tasks[self._id] flag = True for task in tasks: task.enabled = flag task.save() return True def set_disabled(self): tasks = Task.view("couchflow/flowtask", include_docs=True) tasks = tasks[self._id] flag = True for task in tasks: task.enabled = flag task.save() return True def get_first_conector(self): conectors = WorkFlow.view("couchflow/firstconector", include_docs=True) conectors = conectors[self._id] return conectors.one() def get_active_tasks(self): tasks = Task.view("couchflow/activetask", include_docs=True) tasks = tasks[self._id] return tasks def conector_tasks(self, conector): if len(conector.next_tasks) > 0: tasks = [] # TODO: use bulk api to get tasks if not conector.doc_type == "UntilConector": for task_id in conector.next_tasks: task = Task.get(task_id) tasks.append(task) else: task = Task.get(conector.next_tasks[0]) tasks.append(task) return tasks return False def tasks_conectors(self, tasks): conectors = [] for task in tasks: if task.conector_id: conector = get_conector(task.conector_id) #if conector.doc_type == "SequenceConector" and #conector.active: # sequence_conector = SequenceConector.get(conector._id) conectors.append(conector) if len(conectors) > 0: return conectors return False def tasks_tree(self): first_conector = self.get_first_conector() tasks_tree = [ [first_conector], ] tasks = self.conector_tasks(first_conector) while tasks: tasks_tree.append(tasks) conectors = self.tasks_conectors(tasks) if conectors: tasks_tree.append(conectors) tasks = [] for conector in conectors: try: tasks += self.conector_tasks(conector) except: pass else: tasks = False if len(tasks_tree) > 1: return tasks_tree return False def add_conector(self, conector, active=False): self.conectors[conector._id] = active self.save() return True def add_task(self, task, active=False): self.tasks[task._id] = active self.save() return True def remove_tree(self, task): return True
class WFItem(Document): """ WFItem could be any material of the system, like a book or a movie """ _db = get_db("couchflow") name = schema.StringProperty() item_type = schema.StringProperty() is_clone = schema.BooleanProperty(default=False) fields_properties = schema.SchemaDictProperty(Fields) # circulation loan = schema.SchemaProperty(Loan) reserves = schema.SchemaListProperty(Reserve) # Unified Resource Name # We use a sum of fields to generate urn field urn_config = schema.SchemaListProperty(UrnConfig) # Not every item in the system can be loanable loanable = schema.BooleanProperty(default=True) # Order Number to track orders between workflows order_nbr = schema.IntegerProperty() # Reference Item # if it's a reference, it is used to complete fields from other items reference = schema.BooleanProperty(default=False) comments = schema.StringProperty() @property def urn(self): """ Returns the urn based on urn_config property or None if it have not a valid urn """ return get_urn(self) @property def inventory_nbr(self): """ Returns inventory number (876_a) or None """ field = self.get_field("876", "a") if field: return field[0] @property def title(self): """ Returns item title, here for consistency """ title = self.get_field("245", "a") if not title: title = "Unknown title" else: title = title[0] return title def get_field(self, field, subfield=None, first=True): """ Helper that returns field or subfield, or None if can't find it """ return get_field(self, field, subfield, first) def get_info(self): fields = [] for k in self.fields_properties: field_prop = self.fields_properties[k] # TODO: support to more fields first_field_prop = field_prop.first if first_field_prop['exec_value']: value = first_field_prop['exec_value'] k = k + ' - ' + field_prop['field_name'] field = (k, value) fields.append(field) return fields def show_fields_properties(self): """ returns [(sequence number, Fields), ...] """ return enumerate( sorted(self.fields_properties.values(), key=lambda x: x.id and int(x.id))) def fields_properties_items_sorted(self): return sorted(self.fields_properties.items()) def check_form(self, post, task=None): errors = [] def validate_field(number, value, field_type): """ spec is the DocumentSchema for either Field or SubField """ if field_type == 'int': if not value.isdigit(): errors.append([number, field_type]) elif field_type == 'float': try: float(value) except ValueError: errors.append((number, field_type)) for field_id, fields in self.fields_properties.iteritems(): # forms don't support repeated fields yet (TODO elsewhere) field = fields.first field_name = fields.field_name if field_id in post: validate_field(field_name, post[field_id], fields.type) for subfield_id, subfield in field.subfields.iteritems(): sf_full_id = "%s_%s" % (field_id, subfield_id) # TODO: check if "sf_input" in post: validate_field(sf_full_id, post["sf_" + sf_full_id], subfield) if task: for field in task.item_required_fields: if "_" in field: value = post.get("sf_" + field, '') else: value = post.get(field, '') if not value: errors.append([field, 'required']) return errors def marc_record(self): """ Returns the item as a pymarc Record """ record = pymarc.Record() for tag, fprop in self.fields_properties.iteritems(): try: tag = int(tag) except Exception, error: continue # only marc fields if tag > 999: continue if fprop.first.subfields: sfields = [] indicators = [ fprop.first.indicator1 or "#", fprop.first.indicator2 or "#" ] for sf in fprop.first.subfields.values(): for val in sf.exec_value: sfields.append(sf.field_name) sfields.append(val) field = pymarc.Field(tag, indicators, sfields) record.add_field(field) else: try: exec_value = fprop.first.exec_value except Exception: exec_value = [] indicators = [ fprop.first.indicator1 or "#", fprop.first.indicator2 or "#" ] for val in exec_value: record.add_field( pymarc.Field(tag, indicators, data=str(val))) return record
class SubField(BaseField): """ A document schema representing a WFItem subfield """ description = schema.StringProperty()
class MultipleInstances(Conector): iterator_field = schema.StringProperty() def add_next_task(self, task): if len(self.next_tasks) >= 1: return False self.next_tasks.append(task._id) task.prev_conector_id = self._id task.save() self.save() return True def add_previous_task(self, task): if len(self.previous_tasks) > 0: return False self.previous_tasks.append(task._id) return True def check_previous_tasks(self): """ """ def clone_conector(self, conector_id, wfitem_id=False): conector = get_conector(conector_id) try: del (conector._id) except AttributeError: pass try: del (conector._rev) except AttributeError: pass conector.previous_id = conector_id conector.is_clone = True if wfitem_id: conector.wfitems_ids.append(wfitem_id) conector.save() return conector._id def clone_task(self, task_id, wfitem_id=False): task = get_task(task_id) task = task.really_clone() task.previous_id = task_id task.is_clone = True if wfitem_id: task.wfitems_ids = [wfitem_id] task.save() if task.conector_id: task.conector_id = self.clone_conector(task.conector_id, wfitem_id) task.save() conector = get_conector(task.conector_id) conector.previous_tasks[conector.previous_tasks.index( task_id)] = task._id conector.save() next_tasks = [] if conector.doc_type == "UntilConector": next_tasks.append( self.clone_task(conector.next_tasks[0], wfitem_id)) t_id = conector.next_tasks[1] t_task = Task.view("couchflow/wf_oldid", include_docs=True) t_task = t_task[conector.workflow_id] t_task = t_task.all()[0] next_tasks.append(t_task._id) else: for con_task_id in conector.next_tasks: next_tasks.append(self.clone_task(con_task_id, wfitem_id)) conector.next_tasks = next_tasks conector.save() return task._id def create_multiple(self, number): task = get_task(self.next_tasks[0]) number = int(number) for i in range(1, number): new_clone_id = self.clone_task(task._id) new_task = get_task(new_clone_id) new_task.active = True new_task.save() self.next_tasks.append(new_clone_id) self.save() return True def create_multiple_wfitems(self, wfitems): task = get_task(self.next_tasks[0]) for wfitem_id in wfitems: new_clone_id = self.clone_task(task._id, wfitem_id) new_task = get_task(new_clone_id) new_task.active = True new_task.save() self.next_tasks.append(new_clone_id) self.next_tasks.remove(self.next_tasks[0]) self.save() return True def to_next_tasks(self): if len(self.next_tasks) >= 1 and self.previous_tasks: previous_tasks = get_task(self.previous_tasks[0]) if previous_tasks.doc_type == "DynamicDataTask": number = previous_tasks.extra_fields[ self.iterator_field]['exec_value'][0] try: number = int(number) except ValueError: return False self.create_multiple(number) task = Task.get(self.next_tasks[0]) task.active = True task.save() self.save() elif previous_tasks.doc_type == "WFItemsLoader" or \ previous_tasks.doc_type == "FilterWFItems" or\ previous_tasks.doc_type == "CloneItems": self.create_multiple_wfitems(previous_tasks.wfitems_ids) for i in self.next_tasks: task = get_task(i) task.exec_process() return False
class Task(Document): _db = get_db("couchflow") name = schema.StringProperty() task_type = schema.BooleanProperty(default=True) item_type = schema.StringProperty() node_type = schema.StringProperty(default="default") workflow_id = schema.StringProperty() workflow_type = schema.StringProperty() workflow_nro = schema.IntegerProperty(default=0) comments = schema.StringProperty() wfitems_ids = schema.ListProperty() conector_id = schema.StringProperty(default=None) user_id = schema.StringProperty(default=None) saved_by = schema.StringProperty(default=None) group_id = schema.StringProperty(default=None) prev_conector_id = schema.StringProperty(default=None) is_clone = schema.BooleanProperty(default=False) item_fields = schema.DictProperty() item_required_fields = schema.DictProperty() item_tema3_fields = schema.DictProperty() end = schema.BooleanProperty(default=False) step = schema.IntegerProperty(default=0) active = schema.BooleanProperty() have_until = schema.BooleanProperty(default=False) enabled = schema.BooleanProperty(default=False) status = schema.StringProperty() position = schema.DictProperty(default={'x': 50, 'y': 50}) get_id = property(lambda self: self['_id']) # text shown in the task description = schema.StringProperty() group_by_urn = schema.BooleanProperty(default=False) visible = schema.BooleanProperty(default=True) # template used to display tasks, if exists html_tpl = schema.StringProperty(default="") # Used in loader base_item = schema.StringProperty() def is_bulkable(self): for i in self.item_fields: if self.item_fields[i] == 'write': return False return True def exec_process(self): return True @property def item_name(self): name = '' try: item = self.get_item(json=True) field = item['fields_properties']['245']['list'][0] name = field['subfields']['a']['exec_value'][0] except Exception, error: print 'Cant get item_name', error try: item = self.get_item(json=True) field = item['fields_properties']['700']['list'][0] name = field['subfields']['a']['exec_value'][0] except Exception, error: print ' Cant get item_name', error
class User(Document): _db = get_db("couchauth") get_id = property(lambda self: self['_id']) first_name = schema.StringProperty(required=False) last_name = schema.StringProperty(required=False) email = schema.StringProperty(required=False) document_type = schema.StringProperty(required=False) document_number = schema.IntegerProperty(required=False) address = schema.StringProperty(required=False) phone_number = schema.StringProperty(required=False) cellphone_number = schema.StringProperty(required=False) comment = schema.StringProperty(required=False) username = schema.StringProperty() password = schema.StringProperty() groups = schema.ListProperty() is_staff = schema.BooleanProperty() is_superuser = schema.BooleanProperty(default=False) is_active = schema.BooleanProperty(default=True) last_login = schema.DateTimeProperty(required=False) date_joined = schema.DateTimeProperty(default=datetime.utcnow) penalties = schema.SchemaListProperty(Penalty) tematres_host = schema.StringProperty(required=False) def del_group(self, group_id): group = Group.get(group_id) group.del_user(self._id) if group_id in self.groups: del (self.groups[self.groups.index(group_id)]) self.save() def add_group(self, group_id): group = Group.get(group_id) group.add_user(self._id) if group_id not in self.groups: self.groups.append(group_id) self.save() def update_groups(self, groups): for group_id in self.groups: if group_id not in groups: self.del_group(group_id) for group_id in groups: self.add_group(group_id) def id(self): return self._id def set_password(self, password_string): self.password = encrypt_value(password_string) return True def check_password(self, password_string): if self.password == encrypt_value(password_string): return True return False @classmethod def get_user(cls, username, is_active=True): param = {"key": username} user = cls.view('couchauth/username', include_docs=True, **param).first() if user and user.is_active: return user return None @classmethod def get_user_with_groups(cls, username): db = cls.get_db() rows = list( db.view('couchauth/user_and_groups', include_docs=True, startkey=[username, 0], endkey=[username, 1]).all()) if rows: user = cls.wrap(rows[0]['doc']) user.group_names = [x['doc']['name'] for x in rows[1:]] return user @classmethod def get_user_by_email(cls, email, is_active=True): param = {"key": email} user = cls.view('couchauth/get_by_email', include_docs=True, **param).first() if user and user.is_active: return user return None def __unicode__(self): return self.username def __repr__(self): return "<User: %s>" % self.username def is_anonymous(self): return False def save(self): if not self.check_username(): raise Exception('This username is already in use.') if not self.check_email(): raise Exception('This email address is already in use.') return super(self.__class__, self).save() def check_username(self): user = User.get_user(self.username, is_active=None) if user is None: return True return user._id == self._id def check_email(self): if not self.email: return True user = User.get_user_by_email(self.email, is_active=None) if user is None: return True return user._id == self._id def _get_id(self): return self.username id = property(_get_id) def is_authenticated(self): return True