-
-
Notifications
You must be signed in to change notification settings - Fork 278
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into fix-schema-tests
- Loading branch information
Showing
40 changed files
with
1,081 additions
and
222 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# Django Topics app | ||
|
||
Django app that defines models and admin interfaces for editing certain aspects of topics that are unique to Sefaria's product and not needed for general usage of Sefaria's data. | ||
|
||
Currently contains methods to: | ||
- Edit which topics are in which pools | ||
- Define topic of the day schedule | ||
- Define seasonal topic schedule |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
from django.contrib import admin, messages | ||
from django.utils.html import format_html | ||
from django_topics.models import Topic, TopicPool, TopicOfTheDayEnglish, TopicOfTheDayHebrew, SeasonalTopicEnglish, SeasonalTopicHebrew | ||
from django_topics.models.pool import PoolType | ||
|
||
|
||
def create_add_to_pool_action(pool_name): | ||
def add_to_pool(modeladmin, request, queryset): | ||
try: | ||
pool = TopicPool.objects.get(name=pool_name) | ||
for topic in queryset: | ||
topic.pools.add(pool) | ||
modeladmin.message_user(request, f"Added {queryset.count()} topics to {pool.name}", messages.SUCCESS) | ||
|
||
except TopicPool.DoesNotExist: | ||
modeladmin.message_user(request, "The specified pool does not exist.", messages.ERROR) | ||
|
||
add_to_pool.short_description = f"Add selected topics to '{pool_name}' pool" | ||
add_to_pool.__name__ = f"add_to_specific_pool_{pool_name}" | ||
return add_to_pool | ||
|
||
|
||
def create_remove_from_pool_action(pool_name): | ||
def remove_from_pool(modeladmin, request, queryset): | ||
try: | ||
pool = TopicPool.objects.get(name=pool_name) | ||
for topic in queryset: | ||
topic.pools.remove(pool) | ||
modeladmin.message_user(request, f"Removed {queryset.count()} topics from {pool.name}", messages.SUCCESS) | ||
|
||
except TopicPool.DoesNotExist: | ||
modeladmin.message_user(request, "The specified pool does not exist.", messages.ERROR) | ||
|
||
remove_from_pool.short_description = f"Remove selected topics from '{pool_name}' pool" | ||
remove_from_pool.__name__ = f"remove_from_pool_{pool_name}" | ||
return remove_from_pool | ||
|
||
|
||
class PoolFilter(admin.SimpleListFilter): | ||
title = 'Pool Filter' | ||
parameter_name = 'pool' | ||
|
||
def lookups(self, request, model_admin): | ||
return [ | ||
('general_en', 'General Pool EN'), | ||
('general_he', 'General Pool HE'), | ||
(PoolType.TORAH_TAB.value, 'TorahTab Pool'), | ||
] | ||
|
||
def queryset(self, request, queryset): | ||
pool_name = self.value() | ||
if pool_name: | ||
pool = TopicPool.objects.get(name=pool_name) | ||
return queryset.filter(pools=pool) | ||
return queryset | ||
|
||
|
||
@admin.register(Topic) | ||
class TopicAdmin(admin.ModelAdmin): | ||
list_display = ('slug', 'en_title', 'he_title', 'is_in_pool_general_en', 'is_in_pool_general_he', 'is_in_pool_torah_tab', 'sefaria_link') | ||
list_filter = (PoolFilter,) | ||
filter_horizontal = ('pools',) | ||
search_fields = ('slug', 'en_title', 'he_title') | ||
readonly_fields = ('slug', 'en_title', 'he_title') | ||
actions = [ | ||
create_add_to_pool_action('general_en'), | ||
create_add_to_pool_action('general_he'), | ||
create_add_to_pool_action(PoolType.TORAH_TAB.value), | ||
create_remove_from_pool_action('general_en'), | ||
create_remove_from_pool_action('general_he'), | ||
create_remove_from_pool_action(PoolType.TORAH_TAB.value), | ||
] | ||
|
||
def has_add_permission(self, request): | ||
return False | ||
|
||
def has_delete_permission(self, request, obj=None): | ||
return False | ||
|
||
def get_queryset(self, request): | ||
queryset = super().get_queryset(request) | ||
return queryset.filter(pools__name=PoolType.LIBRARY.value) | ||
|
||
def is_in_pool_general_en(self, obj): | ||
return obj.pools.filter(name='general_en').exists() | ||
is_in_pool_general_en.boolean = True | ||
is_in_pool_general_en.short_description = "General Pool EN" | ||
|
||
def is_in_pool_general_he(self, obj): | ||
return obj.pools.filter(name='general_he').exists() | ||
is_in_pool_general_he.boolean = True | ||
is_in_pool_general_he.short_description = "General Pool HE" | ||
|
||
def is_in_pool_torah_tab(self, obj): | ||
return obj.pools.filter(name=PoolType.TORAH_TAB.value).exists() | ||
is_in_pool_torah_tab.boolean = True | ||
is_in_pool_torah_tab.short_description = "TorahTab Pool" | ||
|
||
def sefaria_link(self, obj): | ||
url = f"https://www.sefaria.org/topics/{obj.slug}" | ||
return format_html('<a href="{}" target="_blank">{}</a>', url, obj.slug) | ||
sefaria_link.short_description = "Sefaria Link" | ||
|
||
|
||
class TopicOfTheDayAdmin(admin.ModelAdmin): | ||
exclude = ("lang",) # not for manual editing | ||
list_display = ('start_date', 'topic') | ||
list_filter = ('start_date',) | ||
raw_id_fields = ('topic',) | ||
search_fields = ('topic__slug', 'topic__en_title', 'topic__he_title') | ||
date_hierarchy = 'start_date' | ||
ordering = ['-start_date'] | ||
fieldsets = ( | ||
(None, { | ||
'fields': ('topic', 'start_date'), | ||
}), | ||
) | ||
|
||
def formfield_for_foreignkey(self, db_field, request, **kwargs): | ||
if db_field.name == "topic": | ||
kwargs["label"] = "Topic slug" | ||
kwargs["help_text"] = "Use the magnifying glass button to select a topic." | ||
return super().formfield_for_foreignkey(db_field, request, **kwargs) | ||
|
||
|
||
@admin.register(TopicOfTheDayEnglish) | ||
class TopicOfTheDayAdminEnglish(TopicOfTheDayAdmin): | ||
|
||
def get_queryset(self, request): | ||
qs = super().get_queryset(request) | ||
return qs.filter(lang="en") | ||
|
||
|
||
@admin.register(TopicOfTheDayHebrew) | ||
class TopicOfTheDayAdminHebrew(TopicOfTheDayAdmin): | ||
|
||
def get_queryset(self, request): | ||
qs = super().get_queryset(request) | ||
return qs.filter(lang="he") | ||
|
||
|
||
class SeasonalTopicAdmin(admin.ModelAdmin): | ||
exclude = ("lang",) # not for manual editing | ||
list_display = ( | ||
'start_date', | ||
'topic', | ||
'display_date_prefix', | ||
'display_date_suffix', | ||
'secondary_topic', | ||
'display_start_date_israel', | ||
'display_end_date_israel', | ||
'display_start_date_diaspora', | ||
'display_end_date_diaspora' | ||
) | ||
raw_id_fields = ('topic', 'secondary_topic') | ||
list_filter = ( | ||
'start_date', | ||
'display_start_date_israel', | ||
'display_start_date_diaspora' | ||
) | ||
ordering = ['-start_date'] | ||
search_fields = ('topic__slug', 'topic__en_title', 'topic__he_title', 'secondary_topic__slug') | ||
autocomplete_fields = ('topic', 'secondary_topic') | ||
date_hierarchy = 'start_date' | ||
fieldsets = ( | ||
(None, { | ||
'fields': ( | ||
'topic', | ||
'secondary_topic', | ||
'start_date' | ||
) | ||
}), | ||
('Display Date Prefix/Suffix', { | ||
'fields': ( | ||
'display_date_prefix', | ||
'display_date_suffix', | ||
), | ||
'description': 'Prefix/Suffix that will be displayed around the secondary topic.', | ||
}), | ||
('Israel Display Dates', { | ||
'fields': ( | ||
'display_start_date_israel', | ||
'display_end_date_israel' | ||
), | ||
'description': 'Dates to be displayed to the user of when this topic is "happening". ' | ||
'E.g. for a holiday, when the holiday occurs. ' | ||
'When the dates are the same for both Israel and Diaspora, only fill out Israeli dates. ' | ||
'Similarly, when the start and end dates are the same, only fill out start date.' | ||
}), | ||
('Diaspora Display Dates', { | ||
'fields': ( | ||
'display_start_date_diaspora', | ||
'display_end_date_diaspora' | ||
), | ||
'description': 'When the dates are the same for both Israel and Diaspora, only fill out Israeli dates. ' | ||
'Similarly, when the start and end dates are the same, only fill out start date.' | ||
|
||
}), | ||
) | ||
|
||
def formfield_for_foreignkey(self, db_field, request, **kwargs): | ||
if db_field.name == "topic": | ||
kwargs["label"] = "Topic slug" | ||
kwargs["help_text"] = "Use the magnifying glass button to select a topic." | ||
if db_field.name == "secondary_topic": | ||
kwargs["label"] = "Secondary topic slug" | ||
return super().formfield_for_foreignkey(db_field, request, **kwargs) | ||
|
||
def save_model(self, request, obj, form, change): | ||
""" | ||
Overriding the save_model to ensure the model's clean method is executed. | ||
""" | ||
obj.clean() | ||
super().save_model(request, obj, form, change) | ||
|
||
|
||
@admin.register(SeasonalTopicEnglish) | ||
class SeasonalTopicAdminEnglish(SeasonalTopicAdmin): | ||
|
||
def get_queryset(self, request): | ||
qs = super().get_queryset(request) | ||
return qs.filter(lang="en") | ||
|
||
|
||
@admin.register(SeasonalTopicHebrew) | ||
class SeasonalTopicAdminHebrew(SeasonalTopicAdmin): | ||
def get_queryset(self, request): | ||
qs = super().get_queryset(request) | ||
return qs.filter(lang="he") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class DjangoTopicsAppConfig(AppConfig): | ||
name = "django_topics" | ||
verbose_name = "Topics Management" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
# -*- coding: utf-8 -*- | ||
# Generated by Django 1.11.29 on 2024-11-19 08:18 | ||
from __future__ import unicode_literals | ||
|
||
from django.db import migrations, models | ||
import django.db.models.deletion | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
initial = True | ||
|
||
dependencies = [ | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name='SeasonalTopic', | ||
fields=[ | ||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
('start_date', models.DateField()), | ||
('display_start_date_israel', models.DateField(blank=True, null=True)), | ||
('display_end_date_israel', models.DateField(blank=True, null=True)), | ||
('display_start_date_diaspora', models.DateField(blank=True, null=True)), | ||
('display_end_date_diaspora', models.DateField(blank=True, null=True)), | ||
], | ||
options={ | ||
'verbose_name': 'Seasonal Topic', | ||
'verbose_name_plural': 'Seasonal Topics', | ||
}, | ||
), | ||
migrations.CreateModel( | ||
name='Topic', | ||
fields=[ | ||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
('slug', models.CharField(max_length=255, unique=True)), | ||
('en_title', models.CharField(blank=True, default='', max_length=255)), | ||
('he_title', models.CharField(blank=True, default='', max_length=255)), | ||
], | ||
), | ||
migrations.CreateModel( | ||
name='TopicOfTheDay', | ||
fields=[ | ||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
('start_date', models.DateField()), | ||
('topic', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='topic_of_the_day', to='django_topics.Topic')), | ||
], | ||
options={ | ||
'verbose_name': 'Topic of the Day', | ||
'verbose_name_plural': 'Topics of the Day', | ||
}, | ||
), | ||
migrations.CreateModel( | ||
name='TopicPool', | ||
fields=[ | ||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
('name', models.CharField(max_length=255, unique=True)), | ||
], | ||
), | ||
migrations.AddField( | ||
model_name='topic', | ||
name='pools', | ||
field=models.ManyToManyField(blank=True, related_name='topics', to='django_topics.TopicPool'), | ||
), | ||
migrations.AddField( | ||
model_name='seasonaltopic', | ||
name='secondary_topic', | ||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='seasonal_secondary_topic', to='django_topics.Topic'), | ||
), | ||
migrations.AddField( | ||
model_name='seasonaltopic', | ||
name='topic', | ||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='seasonal_topic', to='django_topics.Topic'), | ||
), | ||
migrations.AlterUniqueTogether( | ||
name='topicoftheday', | ||
unique_together=set([('topic', 'start_date')]), | ||
), | ||
migrations.AlterUniqueTogether( | ||
name='seasonaltopic', | ||
unique_together=set([('topic', 'start_date')]), | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# -*- coding: utf-8 -*- | ||
# Generated by Django 1.11.29 on 2024-11-21 10:17 | ||
from __future__ import unicode_literals | ||
|
||
from django.db import migrations, models | ||
import django.db.models.deletion | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('django_topics', '0001_initial'), | ||
] | ||
|
||
operations = [ | ||
migrations.AlterModelOptions( | ||
name='seasonaltopic', | ||
options={'verbose_name': 'Landing Page - Calendar', 'verbose_name_plural': 'Landing Page - Calendar'}, | ||
), | ||
migrations.AlterModelOptions( | ||
name='topic', | ||
options={'verbose_name': 'Topic Pool Management', 'verbose_name_plural': 'Topic Pool Management'}, | ||
), | ||
migrations.AlterModelOptions( | ||
name='topicoftheday', | ||
options={'verbose_name': 'Landing Page - Topic of the Day', 'verbose_name_plural': 'Landing Page - Topic of the Day'}, | ||
), | ||
migrations.AlterField( | ||
model_name='seasonaltopic', | ||
name='secondary_topic', | ||
field=models.ForeignKey(blank=True, help_text="Secondary topic which will be displayed alongside `topic`. E.g. `topic` is 'Teshuva' then secondary topic could be 'Yom Kippur'.", null=True, on_delete=django.db.models.deletion.CASCADE, related_name='seasonal_secondary_topic', to='django_topics.Topic'), | ||
), | ||
migrations.AlterField( | ||
model_name='seasonaltopic', | ||
name='start_date', | ||
field=models.DateField(help_text='Start date of when this will appear. End date is implied by when the next Seasonal Topic is displayed.'), | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# -*- coding: utf-8 -*- | ||
# Generated by Django 1.11.29 on 2024-11-21 11:57 | ||
from __future__ import unicode_literals | ||
|
||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('django_topics', '0002_auto_20241121_0617'), | ||
] | ||
|
||
operations = [ | ||
migrations.RemoveField( | ||
model_name='topic', | ||
name='id', | ||
), | ||
migrations.AlterField( | ||
model_name='topic', | ||
name='slug', | ||
field=models.CharField(max_length=255, primary_key=True, serialize=False), | ||
), | ||
] |
Oops, something went wrong.