Skip to content

Commit

Permalink
Text groups (#491)
Browse files Browse the repository at this point in the history
  • Loading branch information
lobsam authored Jan 26, 2025
2 parents 23e555e + effa553 commit 440ecf7
Show file tree
Hide file tree
Showing 26 changed files with 567 additions and 72 deletions.
36 changes: 36 additions & 0 deletions mongo_script.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from pymongo import MongoClient

# Replace the URI string with your MongoDB connection string
connection_string = "mongodb://localhost:27017/" # For a local instance
# For MongoDB Atlas, your connection string might look like:
# connection_string = "mongodb+srv://<username>:<password>@cluster0.mongodb.net/test?retryWrites=true&w=majority"

# Create a MongoClient
client = MongoClient(connection_string)

# Access a database
db = client['sefaria']

# Access a collection
index_collection = db['index']


documents = index_collection.find()
texts = []

for doc in documents:
texts.append({
'title': doc['title'],
'text_category': doc['categories'],
})

update_result = db['text_permission_groups'].update_one(
{"name": "default"}, # Query to find the document
{"$push": {"texts": {"$each": texts}}} # Use $push with $each to add multiple items
)

# Print the result
if update_result.modified_count > 0:
print("Texts added successfully.")
else:
print("No matching document found or no update performed.")
14 changes: 14 additions & 0 deletions reader/templatetags/cache_busting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import os
from django import template
from django.conf import settings

register = template.Library()

@register.simple_tag
def versioned_static(file_path):
full_path = os.path.join(settings.BASE_DIR, 'static', file_path)
try:
timestamp = int(os.path.getmtime(full_path))
return f"{settings.STATIC_URL}{file_path}?v={timestamp}"
except FileNotFoundError:
return f"{settings.STATIC_URL}{file_path}"
281 changes: 242 additions & 39 deletions reader/views.py

Large diffs are not rendered by default.

15 changes: 13 additions & 2 deletions sefaria/client/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
logger = structlog.get_logger(__name__)

from sefaria.model import *
from sefaria.system.middleware import get_current_user
from sefaria.system.database import db
from sefaria.datatype.jagged_array import JaggedTextArray
from sefaria.system.exceptions import InputError, NoVersionFoundError
from sefaria.model.user_profile import user_link, public_user_data
Expand Down Expand Up @@ -193,7 +195,11 @@ def get_links(tref, with_text=True, with_sheet_links=False):
node_depth = getattr(source_ref.index_node, "depth", None)
if node_depth is None or len(source_ref.sections) + 1 < node_depth:
continue


linkPos = (pos + 1) % 2
linkTref = link.refs[linkPos]
linkRef = Ref(linkTref)

com = format_link_object_for_client(link, False, nRef, pos)
except InputError:
logger.warning("Bad link: {} - {}".format(link.refs[0], link.refs[1]))
Expand Down Expand Up @@ -283,7 +289,12 @@ def get_links(tref, with_text=True, with_sheet_links=False):
com[versionAttr] = versions
com[licenseAttr] = licenses
com[vtitleInHeAttr] = versionTitlesInHebrew
links.append(com)

user_email = get_current_user()
text_list = library.get_text_permission_group(user_email)
for text in text_list:
if linkRef.index.get_title("en") == text['title']:
links.append(com)
except NoVersionFoundError as e:
logger.warning("Trying to get non existent text for ref '{}'. Link refs were: {}".format(top_nref, link.refs))
continue
Expand Down
26 changes: 21 additions & 5 deletions sefaria/model/category.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from . import schema as schema
from . import text as text
from . import collection as collection
from sefaria.system.middleware import get_current_user


class Category(abstract.AbstractMongoRecord, schema.AbstractTitledOrTermedObject):
Expand Down Expand Up @@ -48,7 +49,10 @@ def _set_derived_attributes(self):
# which should then propagate to the `lastPath` and `sharedTitle`
self.change_key_name(self.path[-1])
self._load_title_group()


def get_category_path(self):
return self.path

def change_key_name(self, name):
# Doesn't yet support going from shared term to local or vise-versa.
if self.sharedTitle and schema.Term().load({"name": name}):
Expand Down Expand Up @@ -310,13 +314,24 @@ def _make_index_node(self, index, old_title=None, mobile=False):
}

return TocTextIndex(d, index_object=index)

def _add_category(self, cat):
try:
from sefaria.model import library
tc = TocCategory(category_object=cat)
parent = self._path_hash[tuple(cat.path[:-1])] if len(cat.path[:-1]) else self._root
parent.append(tc)
self._path_hash[tuple(cat.path)] = tc
user_email = get_current_user()
all_cats = [] # store previously loaded path
text_list = library.get_text_permission_group(user_email)
for c in text_list:
cat_subsets = [c['category'][:i] for i in range(1, len(c['category']) + 1)]
if cat.path in cat_subsets and cat.path not in all_cats:
all_cats.append(cat.path)
parent = self._path_hash[tuple(cat.path[:-1])] if len(cat.path[:-1]) else self._root
parent.append(tc)
self._path_hash[tuple(cat.path)] = tc
# parent = self._path_hash[tuple(cat.path[:-1])] if len(cat.path[:-1]) else self._root
# parent.append(tc)
# self._path_hash[tuple(cat.path)] = tc
except KeyError:
logger.warning(f"Failed to find parent category for {'/'.join(cat.path)}")

Expand Down Expand Up @@ -504,6 +519,7 @@ def __init__(self, serial=None, **kwargs):
self.order = self._index_object.order[0]

def get_index_object(self):
print("index >>>>>>>>>>>>>>>>>>>>>>>>", self._index_object)
return self._index_object

optional_param_keys = [
Expand Down
78 changes: 77 additions & 1 deletion sefaria/model/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -1927,7 +1927,6 @@ def save(self, force_save=False):
)
else:
self.full_version = Version().load({"title": self._oref.index.title, "language": self.lang, "versionTitle": self.vtitle})
print("version:: >>>>>>>>>>>>", self.full_version)
assert self.full_version, "Failed to load Version record for {}, {}".format(self._oref.normal(), self.vtitle)
if self.versionSource:
self.full_version.versionSource = self.versionSource # hack
Expand Down Expand Up @@ -5116,6 +5115,83 @@ def get_toc_tree(self, rebuild=False, mobile=False):
self._toc_tree = TocTree(self, mobile=mobile)
self._toc_tree_is_ready = True
return self._toc_tree

def get_text_permission_group(self, user_email=None):
if user_email:
groups = db.text_permission_groups.find({
"members": {
"$in": [user_email]
}
})
text_list = self.get_user_group_text(groups)
return text_list
else:
text_list = self.get_text_in_default_group()
return text_list

def get_user_group_text(self, groups=[]):
# Track text occurrences and details across all groups
text_counts = {}
text_details = {}

for group in groups:
for text in group['texts']:
title = text['title']
categories = text['text_category']

# Update text_counts and store details
text_counts[title] = text_counts.get(title, 0) + 1
if title not in text_details:
text_details[title] = categories

# Extract unique texts (appear exactly once) with their categories
unique_text_categories = [
{
"title": title,
"category": text_details[title]
}
for title, count in text_counts.items() if count == 1
]

# Collect non-duplicate categories for texts appearing more than once
other_text_categories = [
{
"title": title,
"category": text_details[title]
}
for title, count in text_counts.items() if count > 1
]

# Combine both lists
combined_categories = unique_text_categories + other_text_categories

return combined_categories

def get_text_in_default_group(self):
groups = db.text_permission_groups.find()
# Separate texts in "default" group and others
default_texts = {}
other_texts = set()
for group in groups:
for text in group['texts']:
title = text['title']
categories = text['text_category']

if group["name"] == "default":
default_texts[title] = categories

else:
other_texts.add(title)
# Find texts unique to the "default" group
unique_to_default = [
{
"title": title,
"category": categories
}
for title, categories in default_texts.items() if title not in other_texts
]
return unique_to_default


def get_topic_toc(self, rebuild=False):
"""
Expand Down
1 change: 1 addition & 0 deletions sefaria/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@
'sefaria.system.middleware.ProfileMiddleware',
'sefaria.system.middleware.CORSDebugMiddleware',
'sefaria.system.middleware.SharedCacheMiddleware',
'sefaria.system.middleware.CurrentUserMiddleware',
'sefaria.system.multiserver.coordinator.MultiServerEventListenerMiddleware',
'django_structlog.middlewares.RequestMiddleware',
#'easy_timezones.middleware.EasyTimezoneMiddleware',
Expand Down
5 changes: 5 additions & 0 deletions sefaria/system/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ def ensure_indices(active_db=None):
('groups', ["privateSlug"], {'unique': True}),
('groups', ["members"], {}),
('groups', ["admins"], {}),
('text_permission_groups', ["name"], {'unique': True}),
('text_permission_groups', ["id"], {'unique': True}),
('text_permission_groups', ["texts"], {}),
('text_permission_groups', ["members"], {}),
('text_permission_groups', ["admin"], {}),
('history', ["revision"], {}),
('history', ["method"], {}),
('history', [[("ref", pymongo.ASCENDING), ("version",
Expand Down
28 changes: 28 additions & 0 deletions sefaria/system/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import cProfile
import pstats
from io import StringIO
import threading

from django.conf import settings
from django.utils import translation
Expand All @@ -16,6 +17,7 @@
from sefaria.system.cache import get_shared_cache_elem, set_shared_cache_elem
from django.utils.deprecation import MiddlewareMixin

_thread_local = threading.local()

class SharedCacheMiddleware(MiddlewareMixin):
def process_request(self, request):
Expand Down Expand Up @@ -170,6 +172,32 @@ def current_domain_lang(request):
return domain_lang



class CurrentUserMiddleware:
"""
Middleware to store the current user in thread-local storage for global access.
"""

def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
if request.user.is_authenticated:
_thread_local.user = request.user.email
else:
_thread_local.__dict__.clear()
# Set the current user in thread-local storage
response = self.get_response(request)

return response

def get_current_user():
"""
Retrieve the current user from thread-local storage.
"""
return getattr(_thread_local, 'user', None)


class CORSDebugMiddleware(MiddlewareMixin):
def process_response(self, request, response):
if DEBUG:
Expand Down
2 changes: 2 additions & 0 deletions sefaria/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@
url(r'^api/site_stats/?$', reader_views.site_stats_api),
url(r'^api/manuscripts/(?P<tref>.+)', reader_views.manuscripts_for_source),
url(r'^api/background-data', reader_views.background_data_api),
url(r'^api/text_permission_groups/?(?P<user_email>.+)?$', reader_views.text_permission_groups_api),


]

Expand Down
8 changes: 7 additions & 1 deletion sefaria/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ def process_register_form(request, auth_method='session'):
password=form.cleaned_data['password1'])
profile = UserProfile(id=user.id, user_registration=True)
user_type = form.cleaned_data['user_type']
# Add user to group ("dafault")
update_operation = {
"$push": {
"members": form.cleaned_data["email"]
}
}
db.text_permission_groups.update_one({ "name": "default" }, update_operation)
# add analytics
add_signup_info(email=profile.email,first_name=profile.first_name,last_name=profile.last_name)
profile.assign_slug()
Expand Down Expand Up @@ -146,7 +153,6 @@ def register(request):

if request.method == 'POST':
errors, _, form = process_register_form(request)
print("form >>>>>>>>>>>>>>>>>", form)
if len(errors) == 0:
if "noredirect" in request.POST:
return HttpResponse("ok")
Expand Down
3 changes: 2 additions & 1 deletion static/css/s2.css
Original file line number Diff line number Diff line change
Expand Up @@ -1903,7 +1903,8 @@ div.interfaceLinks-row a {

.readerContent .readerError .readerErrorText {
padding-top: 20px;
font-size: .8em;
font-size: 1.5em;
color:red;
}

.textColumn {
Expand Down
19 changes: 19 additions & 0 deletions static/icons/warning-sign.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 1 addition & 2 deletions static/js/BookPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ class BookPage extends Component {
loadData() {
// Ensures data this text is in cache, rerenders after data load if needed
Sefaria.getIndexDetails(this.props.title).then(data => this.setState({indexDetails: data}));

if (this.isBookToc() && !this.props.compare) {
if(!this.state.versionsLoaded){
Sefaria.getVersions(this.props.title).then(result => {
Expand Down Expand Up @@ -175,7 +174,7 @@ class BookPage extends Component {
const heTitle = index ? index.heTitle : title;
const category = this.props.category;
const isDictionary = this.state.indexDetails && !!this.state.indexDetails.lexiconName;
const categories = Sefaria.index(this.props.title).categories;
const categories = Sefaria.index(this.props.title)? Sefaria.index(this.props.title).categories : [];
let currObjectVersions = this.state.currObjectVersions;
let catUrl;
if (category == "Commentary") {
Expand Down
Loading

0 comments on commit 440ecf7

Please sign in to comment.