Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a8b4d5e
chore: Update requirements for Django 2.x
yitzhakc Feb 18, 2025
59701c7
chore(Topics): Modify `django_topics` migrations to reconcile issues …
yitzhakc Feb 18, 2025
79815e2
chore(emailusernames): Copy py2-py3-django-email-as-username django p…
yitzhakc Feb 18, 2025
e85ef51
chore(emailusernames): Fix forms.py to support Django 2.x
yitzhakc Feb 18, 2025
ae10a0d
chore(gauth): Modify views.py to support Django 2.x
yitzhakc Feb 18, 2025
96c0fc8
chore(admin): Modify urls.py to support Django 2.x
yitzhakc Feb 18, 2025
f219d9e
chore(reader): Modify sefaria_tags.py to prevent the Site model from …
yitzhakc Feb 18, 2025
55203e4
chore(reader): Modify sefaria_tags.py to prevent the Site model from …
yitzhakc Jul 16, 2025
f91f373
Merge master into Django 2.0 upgrade branch
yitzhakc Jul 16, 2025
d83078b
chore: additional modifications to get runserver working
yitzhakc Sep 4, 2025
d3abe08
chore: commit api endpoint tester and analyzer
yitzhakc Sep 4, 2025
9112023
Merge branch 'master' into feature/sc-31678/upgrade-to-django-2-0
yitzhakc Nov 19, 2025
e708090
fix: add adminsortable to requirements
yitzhakc Nov 19, 2025
9de430c
Merge remote-tracking branch 'origin/feature/sc-31679/upgrade-to-djan…
yitzhakc Nov 19, 2025
df033a7
chore: empty commit to base branch
yitzhakc Nov 30, 2025
a904411
Merge branch 'feature/sc-31678/upgrade-to-django-2-0' into feature/sc…
yitzhakc Nov 30, 2025
b29fcbb
fix(urls): Use the Django 2+ functions for including paths
yitzhakc Dec 2, 2025
416e86d
fix(emailusernames): Apply monkey patch via AppConfig.ready() for Dja…
yitzhakc Jan 27, 2026
7b29a0f
fix(emailusernames): Re-apply monkey patch when removed by Django tes…
yitzhakc Jan 27, 2026
2ac855e
chore: fix tests and urls by comments
yitzhakc Jan 28, 2026
02b17ac
chore: trigger CI image build for cauldron
yodem Mar 30, 2026
72eee40
Merge master into django-2-upgrade with conflict resolution and Djang…
yodem Mar 30, 2026
598d262
fix(requirements): remove duplicate package entries causing pip insta…
yodem Mar 30, 2026
5f1ca44
fix(requirements): bump requests to 2.32.5 for langchain-community co…
yodem Mar 30, 2026
33b7cd0
fix(requirements): add missing cryptography==42.0.7
yodem Mar 30, 2026
8199bab
fix(base.html): quote STATIC_PREFIX in DJANGO_VARS to prevent JS synt…
yodem Mar 30, 2026
3956ade
fix(base.html): revert to master version, remove stale Django 2.0 add…
yodem Mar 30, 2026
9f25ae9
fix: revert stale files to master, remove debug scripts
yodem Mar 30, 2026
eebb222
feat: add API test suite for Sefaria endpoints
yodem Mar 31, 2026
b718dbf
feat: rewrite API tests in Python for consistency with project test s…
yodem Mar 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions api-tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import os
import pytest
import requests


API_BASE_URL = os.environ.get("API_BASE_URL", "https://www.sefaria.org")


@pytest.fixture(scope="session")
def base_url():
return API_BASE_URL.rstrip("/")


@pytest.fixture(scope="session")
def api(base_url):
"""Session-scoped requests session with base URL helper."""
session = requests.Session()
session.headers.update({"Accept": "application/json"})
session.base_url = base_url

class ApiClient:
def get(self, path, **kwargs):
kwargs.setdefault("timeout", 30)
return session.get(f"{base_url}{path}", **kwargs)

def post(self, path, **kwargs):
kwargs.setdefault("timeout", 30)
return session.post(f"{base_url}{path}", **kwargs)

return ApiClient()
86 changes: 86 additions & 0 deletions api-tests/test_endpoints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""Core API endpoint tests."""
from urllib.parse import quote


def test_index_titles(api):
r = api.get("/api/index/titles")
assert r.status_code == 200
body = r.json()
titles = body if isinstance(body, list) else body.get("books", [])
assert isinstance(titles, list)
assert len(titles) > 0


def test_index_by_title(api):
r = api.get("/api/index/Genesis")
assert r.status_code == 200
body = r.json()
assert "title" in body
assert "categories" in body


def test_links(api):
r = api.get(f"/api/links/{quote('Genesis 1:1')}")
assert r.status_code in (200, 404)
if r.status_code == 200:
assert isinstance(r.json(), list)


def test_related(api):
r = api.get(f"/api/related/{quote('Genesis 1:1')}")
assert r.status_code in (200, 404)


def test_terms(api):
r = api.get("/api/terms/Torah")
assert r.status_code in (200, 404)


def test_category(api):
r = api.get("/api/category")
assert r.status_code in (200, 404)


def test_translations(api):
r = api.get("/api/texts/translations")
assert r.status_code == 200
body = r.json()
assert isinstance(body, (list, dict))


def test_counts(api):
r = api.get("/api/counts/Genesis")
assert r.status_code in (200, 404)


def test_shape(api):
r = api.get("/api/shape/Genesis")
assert r.status_code in (200, 404)


def test_preview(api):
r = api.get("/api/preview/Genesis")
assert r.status_code in (200, 404)


def test_name(api):
r = api.get("/api/name/Genesis")
assert r.status_code in (200, 404)


def test_topics_list(api):
r = api.get("/api/topics")
assert r.status_code == 200
assert isinstance(r.json(), list)


def test_topic_by_slug(api):
r = api.get("/api/topics/torah")
assert r.status_code in (200, 404)


def test_calendars(api):
r = api.get("/api/calendars")
assert r.status_code == 200
body = r.json()
assert isinstance(body, (list, dict))
24 changes: 24 additions & 0 deletions api-tests/test_health.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""Health and connectivity checks."""


def test_healthz(api):
r = api.get("/healthz")
assert r.status_code in (200, 403)
if r.status_code == 200:
assert len(r.text) > 0


def test_health_check(api):
r = api.get("/health-check")
assert r.status_code == 200


def test_index_returns_toc(api):
r = api.get("/api/index")
assert r.status_code == 200
body = r.json()
if isinstance(body, list):
assert len(body) > 0
assert "contents" in body[0]
else:
assert "contents" in body
35 changes: 35 additions & 0 deletions api-tests/test_profile_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Profile and authentication API tests."""


def test_profile_without_auth(api):
r = api.get("/api/profile")
assert r.status_code in (200, 301, 302, 401, 403, 404)


def test_public_profile(api):
r = api.get("/api/profile/public")
assert r.status_code in (200, 404)


def test_saved_texts(api):
r = api.get("/api/user_history/saved")
assert r.status_code in (200, 401, 403)


def test_user_history(api):
r = api.get("/api/profile/user_history")
assert r.status_code in (200, 401, 403)


def test_register_endpoint_exists(api):
r = api.post("/api/register", json={
"email": "test@example.com",
"username": "testuser",
"password": "password123",
})
assert r.status_code < 500


def test_login_refresh_endpoint_exists(api):
r = api.post("/api/login/refresh/", json={"refresh": "dummy_token"})
assert r.status_code < 500
32 changes: 32 additions & 0 deletions api-tests/test_search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Search API endpoint tests."""


def test_search_page(api):
r = api.get("/search?q=Torah")
assert r.status_code == 200


def test_search_genesis(api):
r = api.get("/search?q=Genesis")
assert r.status_code == 200


def test_search_wrapper_es8(api):
r = api.get("/api/search-wrapper/es8")
assert r.status_code == 200


def test_search_wrapper(api):
r = api.get("/api/search-wrapper")
assert r.status_code == 200


def test_opensearch_suggestions(api):
r = api.get("/api/opensearch-suggestions?q=Gene")
assert r.status_code == 200
assert isinstance(r.json(), list)


def test_search_with_limit(api):
r = api.get("/search?q=Torah&size=5")
assert r.status_code == 200
60 changes: 60 additions & 0 deletions api-tests/test_sheets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""Sheets API endpoint tests."""
from urllib.parse import quote


def test_sheets_by_tag(api):
r = api.get("/api/sheets/tag/Torah")
assert r.status_code in (200, 504)
if r.status_code == 200:
body = r.json()
assert isinstance(body, (list, dict))


def test_trending_tags(api):
r = api.get("/api/sheets/trending-tags")
assert r.status_code == 200
assert isinstance(r.json(), list)


def test_tag_list(api):
r = api.get("/api/sheets/tag-list")
assert r.status_code == 200
assert isinstance(r.json(), list)


def test_all_sheets(api):
r = api.get("/api/sheets/all-sheets/10/0")
assert r.status_code == 200
body = r.json()
assert isinstance(body, (list, dict))


def test_sheets_for_ref(api):
r = api.get(f"/api/sheets/ref/{quote('Genesis 1:1')}")
assert r.status_code == 200
assert isinstance(r.json(), list)


def test_create_sheet_requires_auth(api):
r = api.post("/api/sheets", json={"title": "Test Sheet", "sources": []})
assert r.status_code < 500


def test_get_sheet_by_id(api):
r = api.get("/api/sheets/1")
assert r.status_code in (200, 404)


def test_user_sheets(api):
r = api.get("/api/sheets/user/1")
assert r.status_code == 200
body = r.json()
assert isinstance(body, (list, dict))


def test_v2_sheets_by_tag(api):
r = api.get("/api/v2/sheets/tag/Torah")
assert r.status_code in (200, 504)
if r.status_code == 200:
body = r.json()
assert isinstance(body, (list, dict))
61 changes: 61 additions & 0 deletions api-tests/test_texts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""Text API endpoint tests."""
import pytest
from urllib.parse import quote


TEXT_CASES = [
("Genesis 1:1", "First verse of Torah"),
("Genesis 1:1-5", "First 5 verses"),
("Exodus 20:1", "10 Commandments start"),
("Psalms 23", "Psalm 23"),
("Berakhot 2a", "Talmud page"),
]


@pytest.mark.parametrize("ref,description", TEXT_CASES, ids=[t[0] for t in TEXT_CASES])
def test_get_text(api, ref, description):
r = api.get(f"/api/texts/{quote(ref)}")
assert r.status_code == 200
body = r.json()
assert "ref" in body
assert "text" in body
assert "he" in body
assert "heRef" in body
assert "sections" in body
assert body["ref"]


def test_get_text_with_version(api):
r = api.get(f"/api/texts/{quote('Genesis 1:1')}?version=Leningrad%20Codex")
assert r.status_code == 200
assert "ref" in r.json()


def test_get_text_with_commentary(api):
r = api.get(f"/api/texts/{quote('Genesis 1:1')}?commentary=1", timeout=30)
assert r.status_code in (200, 404, 504)


def test_get_random_text(api):
r = api.get("/api/texts/random")
assert r.status_code == 200
body = r.json()
assert "ref" in body
assert "text" in body


def test_get_versions(api):
r = api.get("/api/versions")
assert r.status_code in (200, 404)


def test_get_text_versions(api):
r = api.get(f"/api/texts/versions/{quote('Genesis 1:1')}")
assert r.status_code == 200
body = r.json()
assert isinstance(body, (list, dict))


def test_invalid_ref(api):
r = api.get(f"/api/texts/{quote('InvalidBookName 999:999')}")
assert r.status_code in (200, 404)
3 changes: 3 additions & 0 deletions emailusernames/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
__version__ = '1.7.1'

default_app_config = 'emailusernames.apps.EmailUsernamesConfig'
37 changes: 37 additions & 0 deletions emailusernames/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""
Override the add- and change-form in the admin, to hide the username.
"""
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
from django.contrib import admin
from emailusernames.forms import EmailUserCreationForm, EmailUserChangeForm
from django.utils.translation import ugettext_lazy as _


class EmailUserAdmin(UserAdmin):
add_form = EmailUserCreationForm
form = EmailUserChangeForm

add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('email', 'password1', 'password2')}
),
)
fieldsets = (
(None, {'fields': ('email', 'password')}),
(_('Personal info'), {'fields': ('first_name', 'last_name')}),
(_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser', 'user_permissions')}),
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
(_('Groups'), {'fields': ('groups',)}),
)
list_display = ('email', 'first_name', 'last_name', 'is_staff')
ordering = ('email',)


admin.site.unregister(User)
admin.site.register(User, EmailUserAdmin)


def __email_unicode__(self):
return self.email
12 changes: 12 additions & 0 deletions emailusernames/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from django.apps import AppConfig


class EmailUsernamesConfig(AppConfig):
name = 'emailusernames'
verbose_name = 'Email Usernames'

def ready(self):
# Apply monkey patches after all apps are loaded
# This is the Django 2.0+ recommended way to do monkey patching
from emailusernames.models import monkeypatch_user
monkeypatch_user()
Loading
Loading