def toNumber(self, lang, s): if lang == "en": try: if s[-1] in ["a", "b"]: amud = s[-1] daf = int(s[:-1]) else: amud = "a" daf = int(s) except ValueError: raise InputError( u"Couldn't parse Talmud reference: {}".format(s)) if self.length and daf > self.length: #todo: Catch this above and put the book name on it. Proably change Exception type. raise InputError(u"{} exceeds max of {} dafs.".format( daf, self.length)) indx = daf * 2 if amud == "a": indx -= 1 return indx elif lang == "he": num = re.split("[.:,\s]", s)[0] daf = decode_hebrew_numeral(num) * 2 if s[-1] == ":" or ( s[-1] == u"\u05d1" #bet and ((len(s) > 2 and s[-2] in ", ") # simple bet or (len(s) > 4 and s[-3] == u'\u05e2') # ayin"bet or (len(s) > 5 and s[-4] == u"\u05e2") # ayin''bet )): return daf # amud B return daf - 1
def _pre_save(self): self.lastModified = datetime.now() old_status, new_status = self.pkeys_orig_values.get( "listed", None), getattr(self, "listed", None) if (old_status != new_status or not getattr(self, "slug", None)): # Assign a slug to new collection, or change the slug type when collection # changes listing status self.assign_slug() if new_status and not old_status: # At moment of publishing, make checks for special requirements on public collections # Don't make these checks on every save so a collection can't get stuck in a state where # it can't be change even to add a new public sheet. if self.name_taken(): # Require public collections to have a unique name raise InputError( _("A public collection with this name already exists. Please choose a different name before publishing." )) if not getattr(self, "imageUrl", False): raise InputError( _("Public Collections are required to include a collection image (a square image will work best)." )) if self.public_sheet_count() < 3: raise InputError( _("Public Collections are required to have at least 3 public sheets." )) image_fields = ("imageUrl", "headerUrl", "coverUrl") for field in image_fields: old, new = self.pkeys_orig_values.get(field, None), getattr( self, field, None) if old != new: self._handle_image_change(old, new)
def _pre_save(self): old_status, new_status = self.pkeys_orig_values.get("listed", None), getattr(self, "listed", None) if new_status and not old_status: # Collection is being published, assign a new slug, but save the old one for link stability self.privateSlug = self.slug self.assign_slug() if old_status and not new_status: # Public collection is going back to private, restore old slug if getattr(self, "privateSlug", None): self.slug = self.privateSlug else: self.assign_slug() if new_status and not old_status: # At moment of publishing, make checks for special requirements on public collections # Don't make these checks on every save so a collection can't get stuck in a state where # it can't be change even to add a new public sheet. if self.name_taken(): # Require public collections to have a unique name raise InputError(_("A public collection with this name already exists. Please choose a different name before publishing.")) if not getattr(self, "imageUrl", False): raise InputError(_("Public Collections are required to include a collection image (a square image will work best).")) if self.public_sheet_count() < 3: raise InputError(_("Public Collections are required to have at least 3 public sheets.")) image_fields = ("imageUrl", "headerUrl", "coverUrl") for field in image_fields: old, new = self.pkeys_orig_values.get(field, None), getattr(self, field, None) if old != new: self._handle_image_change(old, new)
def modify_tanakh_links_one(main_ref, section_map, error_file_csv, user): try: main_tc, main_oref, main_version = get_tc(main_ref) if main_tc.is_merged: # not really possible but whatevs return # to make it _savable main_tc._saveable = True new_main_text = main_tc.text edited = False for section_tref, segment_ref_dict in list(section_map.items()): section_oref = get_tc(section_tref, True) quoted_list_temp = sorted(list(segment_ref_dict.items()), key=lambda x: x[0]) segment_ref_list = [ segment_ref_dict.get(i, None) for i in range(quoted_list_temp[-1][0] + 1) ] # for r in segment_ref_list: # if r is None: # continue # l = Link().load({"generated_by": "link_disambiguator", "refs": [main_ref, r.normal()]}) # if l and l.generated_by != "add_links_from_text": # l.generated_by = "add_links_from_text" # l.save() match_list = get_snippet_by_seg_ref(main_tc, section_oref, must_find_snippet=True, snip_size=65, return_matches=True) if match_list: if len(match_list) == len(segment_ref_list): last_find = 0 for m, r in zip(match_list, segment_ref_list): if r is None: # None is how I represent a bad match continue original = m.group(2) curr_find = new_main_text.find(original, last_find) if curr_find == -1: raise InputError("RefNotFound") replacement = r.he_normal() new_main_text = "{}{}{}".format( new_main_text[:curr_find], replacement, new_main_text[curr_find + len(original):]) edited = True last_find = curr_find + len(replacement) else: raise InputError("DiffLen") if edited: modify_text(user, main_oref, main_version.versionTitle, main_version.language, new_main_text, main_version.versionSource) except InputError as e: message = e.args[0] error_file_csv.writerow({"Quoting Ref": main_ref, "Error": message})
def _validate(self): assert super(Group, self)._validate() if getattr(self, "listed", False): if not getattr(self, "imageUrl", False): raise InputError(_("Public Groups are required to include a group image (a square image will work best).")) contents = self.contents(with_content=True) if len(contents["sheets"]) < 3: raise InputError(_("Public Groups are required to have at least 3 public sheets.")) return True
def _validate(self): super(Category, self)._validate() assert self.lastPath == self.path[-1] == self.get_primary_title("en"), "Category name not matching" + " - " + self.lastPath + " / " + self.path[-1] + " / " + self.get_primary_title("en") if not self.sharedTitle and not self.get_titles_object(): raise InputError("Category {} must have titles or a shared title".format(self)) try: self.title_group.validate() except InputError as e: raise InputError("Category {} has invalid titles: {}".format(self, e)) if self.sharedTitle and schema.Term().load({"name": self.sharedTitle}).titles != self.get_titles_object(): raise InputError("Category {} with sharedTitle can not have explicit titles".format(self))
def modify_text(user, oref, vtitle, lang, text, vsource=None, **kwargs): """ Updates a chunk of text, identified by oref, versionTitle, and lang, and records history. :param user: :param oref: :param vtitle: :param lang: :param text: :param vsource: :return: """ chunk = model.TextChunk(oref, lang, vtitle) if getattr(chunk.version(), "status", "") == "locked" and not is_user_staff(user): raise InputError("This text has been locked against further edits.") action = kwargs.get("type") or "edit" if chunk.text else "add" old_text = chunk.text chunk.text = text if vsource: chunk.versionSource = vsource # todo: log this change if chunk.save(): model.log_text(user, action, oref, lang, vtitle, old_text, text, **kwargs) from sefaria.helper.link import add_commentary_links, add_links_from_text # Commentaries generate links to their base text automatically if oref.type == "Commentary": add_commentary_links(oref, user, **kwargs) # scan text for links to auto add add_links_from_text(oref.normal(), lang, chunk.text, chunk.full_version._id, user, **kwargs) return chunk
def modify_text(user, oref, vtitle, lang, text, vsource=None, **kwargs): """ Updates a chunk of text, identified by oref, versionTitle, and lang, and records history. :param user: :param oref: :param vtitle: :param lang: :param text: :param vsource: :return: """ chunk = model.TextChunk(oref, lang, vtitle) if getattr(chunk.version(), "status", "") == "locked" and not model.user_profile.is_user_staff(user): raise InputError("This text has been locked against further edits.") action = kwargs.get("type") or "edit" if chunk.text else "add" old_text = chunk.text chunk.text = text if vsource: chunk.versionSource = vsource # todo: log this change if chunk.save(): post_modify_text(user, action, oref, lang, vtitle, old_text, chunk.text, chunk.full_version._id, **kwargs) return chunk
def cascade(set_class, attr): """ Handles generic value cascading, for simple key reference changes. See examples in dependencies.py :param set_class: The set class of the impacted model :param attr: The name of the impacted class attribute (fk) that holds the references to the changed attribute (pk) There is support for nested attributes one level deep, e.g. "contents.value" :return: a function that will update 'attr' in 'set_class' and can be passed to subscribe() """ attrs = attr.split(".") if len(attrs) == 1: return lambda obj, **kwargs: set_class({ attr: kwargs["old"] }).update({attr: kwargs["new"]}) elif len(attrs) == 2: def foo(obj, **kwargs): for rec in set_class({attr: kwargs["old"]}): new_dict = { k: (v if k != attrs[1] else kwargs["new"]) for k, v in list(getattr(rec, attrs[0]).items()) } setattr(rec, attrs[0], new_dict) rec.save() return foo else: raise InputError( "cascade does not support attributes deeper than two levels")
def _validate(self): assert super(Collection, self)._validate() if len(self.name) == 0: raise InputError(_("Please set a name for your collection.")) return True
def _validate(self): """ Test self for validity :return: True on success Throws Exception on failure """ attrs = vars(self) """" This fails when the object has been created but not yet saved. if not getattr(self, self.id_field, None): logger.debug(type(self).__name__ + ".is_valid: No id field " + self.id_field + " found.") return False """ for attr in self.required_attrs: #properties. which are virtual instance members, do not get returned by vars() if attr not in attrs and not getattr(self, attr, None): raise InputError( type(self).__name__ + "._validate(): Required attribute: " + attr + " not in " + ", ".join(attrs)) """ This check seems like a good idea, but stumbles as soon as we have internal attrs for attr in attrs: if attr not in self.required_attrs and attr not in self.optional_attrs and attr != self.id_field: logger.debug(type(self).__name__ + ".is_valid: Provided attribute: " + attr + " not in " + ",".join(self.required_attrs) + " or " + ",".join(self.optional_attrs)) return False """ return True
def validate(self): super(TitledTreeNode, self).validate() if any((c in '-') for c in self.title_group.primary_title("en")): raise InputError("Primary English title may not contain hyphens.") if not self.default and not self.sharedTitle and not self.get_titles(): raise IndexSchemaError( "Schema node {} must have titles, a shared title node, or be default" .format(self)) if self.default and (self.get_titles() or self.sharedTitle): raise IndexSchemaError( "Schema node {} - default nodes can not have titles".format( self)) if not self.default and not self.primary_title("en"): raise IndexSchemaError( "Schema node {} missing primary English title".format(self)) if self.has_children() and len([c for c in self.children if c.default ]) > 1: raise IndexSchemaError( "Schema Structure Node {} has more than one default child.". format(self.key)) if self.sharedTitle and Term().load({ "name": self.sharedTitle }).titles != self.get_titles(): raise IndexSchemaError( "Schema node {} with sharedTitle can not have explicit titles". format(self))
def point_location(self, lon=None, lat=None): if lat is None and lon is None: return getattr(self, "point", None) if lat is None or lon is None: raise InputError( "Bad coordinates passed to Place.point_location: {}, {}". format(lon, lat)) self.point = geojson.Point((lon, lat))
def __init__(self, email=None, user_obj=None): if email: self.user = get_user(email) elif user_obj: self.user = user_obj else: raise InputError("No user provided") self._errors = []
def delete(self): if self.is_new(): raise InputError( u"Can not delete {} that doesn't exist in database.".format( type(self).__name__)) notify(self, "delete") getattr(db, self.collection).remove({"_id": self._id})
def delete_user_history(uid, exclude_saved=True, exclude_last_place=False): if not uid: raise InputError("Cannot delete user history without an id") query = {"uid": uid} if exclude_saved: query["saved"] = False if exclude_last_place: query["last_place"] = False UserHistorySet(query).delete(bulk_delete=True)
def delete(self): if self.is_new(): raise InputError("Can not delete {} that doesn't exist in database.".format(type(self).__name__)) #if self.track_pkeys: # for pkey in self.pkeys: # self.pkeys_orig_values[pkey] = getattr(self, pkey) getattr(db, self.collection).remove({"_id": self._id}) notify(self, "delete")
def update(self, query, attrs): """ :param query: Query to find existing object to update. :param attrs: Dictionary of attributes to update. :return: the object """ if not self.load(query): raise InputError("No existing {} record found to update for {}".format(type(self).__name__, str(query))) self.load_from_dict(attrs) return self.save()
def extract_categories_from_title(self): category_pattern = self.categories_delim + r"(.*)" + self.categories_delim title = self.outline.get("text") category_str = re.search(category_pattern, title) if category_str: categories = [s.strip() for s in category_str.group(1).split(",")] self.outline.set('text', re.sub(category_pattern, "", title)) return categories raise InputError( "Categories must be supplied on the Workflowy outline according to specifications" )
def _validate(self): assert super(Group, self)._validate() reserved_chars = ['-', '_', '|'] if any([c in self.name for c in reserved_chars]): raise InputError( _('Group names may not contain the following characters:') + ' {}'.format(', '.join(reservedChars))) if getattr(self, "listed", False): if not getattr(self, "imageUrl", False): raise InputError( _("Public Groups are required to include a group image (a square image will work best)." )) contents = self.contents(with_content=True) if len(contents["sheets"]) < 3: raise InputError( _("Public Groups are required to have at least 3 public sheets." )) return True
def delete(self, force=False): if not self.can_delete(): if force: logger.error(u"Forcing delete of {}.".format(str(self))) else: logger.error(u"Failed to delete {}.".format(str(self))) return if self.is_new(): raise InputError(u"Can not delete {} that doesn't exist in database.".format(type(self).__name__)) notify(self, "delete") getattr(db, self.collection).remove({"_id": self._id})
def modify_text(user, oref, vtitle, lang, text, vsource=None, **kwargs): """ Updates a chunk of text, identified by oref, versionTitle, and lang, and records history. :param user: :param oref: :param vtitle: :param lang: :param text: :param vsource: :return: """ chunk = model.TextChunk(oref, lang, vtitle) if getattr(chunk.version(), "status", "") == "locked" and not model.user_profile.is_user_staff(user): raise InputError("This text has been locked against further edits.") action = kwargs.get("type") or "edit" if chunk.text else "add" old_text = chunk.text chunk.text = text if vsource: chunk.versionSource = vsource # todo: log this change if chunk.save(): model.log_text(user, action, oref, lang, vtitle, old_text, text, **kwargs) if USE_VARNISH: invalidate_ref(oref, lang=lang, version=vtitle, purge=True) if oref.next_section_ref(): invalidate_ref(oref.next_section_ref(), lang=lang, version=vtitle, purge=True) if oref.prev_section_ref(): invalidate_ref(oref.prev_section_ref(), lang=lang, version=vtitle, purge=True) if not kwargs.get("skip_links", None): from sefaria.helper.link import add_links_from_text # Some commentaries can generate links to their base text automatically linker = oref.autolinker(user=user) if linker: linker.refresh_links(**kwargs) # scan text for links to auto add add_links_from_text(oref, lang, chunk.text, chunk.full_version._id, user, **kwargs) if USE_VARNISH: invalidate_linked(oref) return chunk
def __init__(self, title=None, snode=None, _obj=None): if title: snode = library.get_schema_node(title) if not snode: snode = library.get_commentary_schema_node(title) if not snode: raise InputError(u"Can not resolve name: {}".format(title)) self.snode = snode self.versionState = VersionState(snode.index.title) self.d = self.versionState.content_node(snode) elif snode: self.snode = snode self.versionState = VersionState(snode.index.title) self.d = self.versionState.content_node(snode) elif _obj: self.d = _obj
def format_object_for_client(obj, with_text=True, ref=None, pos=None): """ Assumption here is that if obj is a Link, and ref and pos are not specified, then position 0 is the root ref. :param obj: :param ref: :param pos: :return: """ if isinstance(obj, Note): return format_note_object_for_client(obj) elif isinstance(obj, Link): if not ref and not pos: ref = obj.refs[0] pos = 0 return format_link_object_for_client(obj, with_text, ref, pos) else: raise InputError("{} not valid for format_object_for_client".format(obj.__class__.__name__))
def __init__(self, title=None, snode=None, _obj=None, meta=False, hint=None): """ :param title: :param snode: :param _obj: :param meta: If true, returns only the overview information, and not the detailed counts :param hint: hint - a list of (lang, key) tuples of pieces of VersionState to return :return: """ if title: snode = library.get_schema_node(title) if not snode: snode = library.get_schema_node(title) if not snode: raise InputError(u"Can not resolve name: {}".format(title)) if snode.is_default(): snode = snode.parent if snode: proj = None if meta: if snode.parent: raise Exception( "StateNode.meta() only supported for Index roots. Called with {} / {}" .format(title, snode.primary_title("en"))) proj = self.meta_proj if hint: hint_proj = {} base = [VersionState.content_attr] + snode.version_address() for l, k in hint: hint_proj[".".join(base + [self.lang_map[l]] + [k])] = 1 if proj: proj.update(hint_proj) else: proj = hint_proj self.snode = snode self.versionState = VersionState(snode.index.title, proj=proj) self.d = self.versionState.content_node(snode) elif _obj: self.d = _obj
def get_tc(tref, just_ref=False, tries=0): try: main_oref = Ref(tref) if just_ref: return main_oref vset = VersionSet(main_oref.condition_query("he"), proj=main_oref.part_projection()) if len(vset) < 1: raise InputError("VSET not equal to 1") main_version = vset[0] main_tc = main_oref.text("he", vtitle=main_version.versionTitle) return main_tc, main_oref, main_version except AutoReconnect: if tries < 200: time.sleep(0.24) return get_tc(tref, just_ref=just_ref, tries=tries + 1) else: raise AutoReconnect( "Tried so hard but got so many autoreconnects...")
def validate_text(text, tref): """ validate a dictionary representing a text to be written to db.texts """ # Required Keys for key in ("versionTitle", "versionSource", "language", "text"): if not key in text: return {"error": "Field '%s' missing from posted JSON." % key} oref = model.Ref(tref) # Validate depth of posted text matches expectation posted_depth = 0 if isinstance(text["text"], basestring) else list_depth( text["text"]) implied_depth = len(oref.sections) + posted_depth if implied_depth != oref.index.textDepth: raise InputError( u"Text Structure Mismatch. The stored depth of {} is {}, but the text posted to {} implies a depth of {}." .format(oref.book, oref.index.textDepth, tref, implied_depth)) return {"status": "ok"}
def _validate(self): assert super(Link, self)._validate() if False in self.refs: return False if hasattr(self, "charLevelData"): try: assert type(self.charLevelData) is list assert len(self.charLevelData) == 2 assert type(self.charLevelData[0]) is dict assert type(self.charLevelData[1]) is dict except AssertionError: raise InputError( f'Structure of the charLevelData in Link is wrong. link refs: {self.refs[0]} - {self.refs[1]}. charLevelData should be a list of length 2 containing 2 dicts coresponding to the refs list, each dict consists of the following keys: ["startChar","endChar","versionTitle","language"]' ) assert self.charLevelData[0]['versionTitle'] in [ v['versionTitle'] for v in text.Ref(self.refs[0]).version_list() ], 'Dictionaries in charLevelData should be in correspondence to the "refs" list' return True
def _validate(self): from sefaria.model.text import library, Version if self.type == "index": assert self.content.get("index") assert library.get_index(self.content.get("index")) elif self.type == "version": i = self.content.get("index") v = self.content.get("version") l = self.content.get("language") assert i and v and l version = Version().load({ "title": i, "versionTitle": v, "language": l, }) assert version, "No Version Found: {}/{}/{}".format(i, v, l) elif self.type == "general": assert self.content.get("en"), "Please provide an English message." assert self.content.get("he"), "Please provide a Hebrew message." else: raise InputError("Unknown type for GlobalNotification: {}".format(self.type))
def delete(self, force=False, override_dependencies=False): """ Just before the delete is executed, will emit a 'delete' notification. :param force: delete object, even if it fails a `can_delete()` check :param override_dependencies: if override_dependencies is set to True, no notifications will be emitted. :return: """ if not self.can_delete(): if force: logger.error("Forcing delete of {}.".format(str(self))) else: logger.error("Failed to delete {}.".format(str(self))) return if self.is_new(): raise InputError( "Can not delete {} that doesn't exist in database.".format( type(self).__name__)) if not override_dependencies: notify(self, "delete") getattr(db, self.collection).delete_one({"_id": self._id})