예제 #1
0
    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)
예제 #3
0
    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)
예제 #4
0
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})
예제 #5
0
    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
예제 #6
0
    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))
예제 #7
0
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
예제 #8
0
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
예제 #9
0
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")
예제 #10
0
    def _validate(self):
        assert super(Collection, self)._validate()

        if len(self.name) == 0:
            raise InputError(_("Please set a name for your collection."))

        return True
예제 #11
0
    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
예제 #12
0
    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))
예제 #13
0
 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))
예제 #14
0
 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 = []
예제 #15
0
    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})
예제 #16
0
 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)
예제 #17
0
    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")
예제 #18
0
 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()
예제 #19
0
 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"
     )
예제 #20
0
    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
예제 #21
0
    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})
예제 #22
0
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
예제 #23
0
 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
예제 #24
0
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__))
예제 #25
0
 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
예제 #26
0
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...")
예제 #27
0
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"}
예제 #28
0
    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
예제 #29
0
    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))
예제 #30
0
    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})