Skip to content

Commit

Permalink
feat: Implemented Total Read Minutes for each Article
Browse files Browse the repository at this point in the history
  • Loading branch information
happychuks committed Aug 30, 2024
1 parent bb02d68 commit 938bea3
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 73 deletions.
32 changes: 18 additions & 14 deletions server/apps/research/admin/article_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,19 @@
from apps.research.models import Article, Author
from tinymce.widgets import TinyMCE

@admin.register(Article)
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = '__all__'

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['acknowledgement'].widget = TinyMCE(attrs={'cols': 80, 'rows': 30, 'id': "acknowledgement_richtext_field", 'placeholder': f"Enter Acknowledgement here"})
self.fields['content'].widget = TinyMCE(attrs={'cols': 80, 'rows': 30, 'id': "content_richtext_field", 'placeholder': f"Enter Article Content here"})

class ArticleAdmin(admin.ModelAdmin):
"""Admin interface for the Article model."""
form = ArticleForm
fieldsets = [
('Article Details', {'fields': ['title', 'authors', 'acknowledgement', 'categories', 'thumb', 'content', 'summary', 'status', 'scheduled_publish_time']}),
]
Expand All @@ -15,25 +25,17 @@ class ArticleAdmin(admin.ModelAdmin):
list_filter = ('authors', 'status', 'categories', 'created_at')
readonly_fields = ('views', 'slug')
list_editable = ('status',)

def get_form(self, request, obj=None, **kwargs):
"""Return a form with TinyMCE Widget for the selected fields."""
form = super().get_form(request, obj, **kwargs)
for field_name in ['content', 'acknowledgement']:
if field_name in form.base_fields:
form.base_fields[field_name].widget = TinyMCE(attrs={'cols': 80, 'rows': 30, 'id': f"{field_name}_richtext_field", 'placeholder': f"Enter {field_name} here"})
return form


def display_authors(self, obj):
"""Return a comma-separated list of authors for the article."""
return ", ".join(author.user.username for author in obj.authors.all())
display_authors.short_description = 'Authors'

def display_categories(self, obj):
"""Return a comma-separated list of categories for the article."""
return ", ".join(category.name for category in obj.categories.all())
display_categories.short_description = 'Categories'

def save_model(self, request, obj, form, change):
"""Automatically add the logged-in user as the author when creating a new article."""
if not change: # If creating a new article
Expand All @@ -43,7 +45,7 @@ def save_model(self, request, obj, form, change):
obj.authors.add(author)
else:
super().save_model(request, obj, form, change)

def has_change_permission(self, request, obj=None):
"""Check if the user has permission to change the article."""
if request.user.is_superuser:
Expand All @@ -53,7 +55,7 @@ def has_change_permission(self, request, obj=None):
if obj is not None and not obj.authors.filter(user=request.user).exists():
return False
return True

def has_delete_permission(self, request, obj=None):
"""Check if the user has permission to delete the article."""
if request.user.is_superuser:
Expand All @@ -63,3 +65,5 @@ def has_delete_permission(self, request, obj=None):
if obj is not None and not obj.authors.filter(user=request.user).exists():
return False
return True

admin.site.register(Article, ArticleAdmin)
6 changes: 3 additions & 3 deletions server/apps/research/models/article.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ class Article(BaseModel):
class Meta:
ordering = ('-scheduled_publish_time',)

@property
def min_read(self):
# Compute reading time based on content length
def calculate_min_read(self):
word_count = len(self.content.split())
words_per_minute = 300 # Average reading speed (words per minute)
minutes = max(1, round(word_count / words_per_minute))
return minutes

min_read = property(calculate_min_read)

def __str__(self):
return self.title

Expand Down
4 changes: 3 additions & 1 deletion server/apps/research/serializers/article_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ class ArticleSerializer(serializers.ModelSerializer):
categories = CategorySerializer(many=True)
slug = serializers.ReadOnlyField()
views = serializers.ReadOnlyField()
min_read = serializers.ReadOnlyField()


class Meta:
model = Article
fields = [
'id', 'slug', 'title', 'authors', 'thumb',
'categories', 'summary', 'acknowledgement', 'content',
'categories', 'summary', 'acknowledgement', 'content', 'min_read',
'status', 'views', 'created_at', 'updated_at', 'scheduled_publish_time'
]

Expand Down
136 changes: 81 additions & 55 deletions server/templates/admin/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,63 +3,89 @@

{% block footer %}
{{ block.super }}
<script src="https://cdn.tiny.cloud/1/{{ TINYMCE_API_KEY }}/tinymce/7/tinymce.min.js"
<script src="https://cdn.tiny.cloud/1/{{ TINYMCE_API_KEY }}/tinymce/5/tinymce.min.js"
referrerpolicy="origin"></script>
<script>

tinymce.init({
selector: '#richtext_field',
plugins: 'print preview importcss tinydrive searchreplace autolink autosave save directionality visualblocks visualchars fullscreen image link media template codesample table charmap hr pagebreak nonbreaking anchor toc insertdatetime advlist lists wordcount imagetools textpattern noneditable help charmap quickbars emoticons blockquote',
menubar: 'file edit view insert format tools table tc help',
toolbar: 'undo redo | bold italic underline strikethrough | fontselect fontsizeselect formatselect | alignleft aligncenter alignright alignjustify | outdent indent | numlist bullist checklist | forecolor backcolor casechange permanentpen formatpainter removeformat | pagebreak | charmap emoticons | fullscreen preview save print | insertfile image media pageembed template link anchor codesample | a11ycheck ltr rtl | showcomments addcomment',
autosave_ask_before_unload: true,
autosave_interval: '30s',
autosave_prefix: '{path}{query}-{id}-',
autosave_restore_when_empty: false,
autosave_retention: '2m',
image_advtab: true,
automatic_uploads: false,
link_list: [
{title: 'My page 1', value: 'https://www.tiny.cloud'},
{title: 'My page 2', value: 'http://www.moxiecode.com'}
],
image_list: [
{title: 'My page 1', value: 'https://www.tiny.cloud'},
{title: 'My page 2', value: 'http://www.moxiecode.com'}
],
image_class_list: [
{title: 'None', value: ''},
{title: 'Some class', value: 'class-name'}
],
importcss_append: true,
templates: [
{
title: 'New Table',
description: 'creates a new table',
content: '<div class="mceTmpl"><table width="98%%" border="0" cellspacing="0" cellpadding="0"><tr><th scope="col"> </th><th scope="col"> </th></tr><tr><td> </td><td> </td></tr></table></div>'
},
{title: 'Starting my story', description: 'A cure for writers block', content: 'Once upon a time...'},
{
title: 'New list with dates',
description: 'New List with dates',
content: '<div class="mceTmpl"><span class="cdate">cdate</span><br /><span class="mdate">mdate</span><h2>My List</h2><ul><li></li><li></li></ul></div>'
}
],
template_cdate_format: '[Date Created (CDATE): %m/%d/%Y : %H:%M:%S]',
template_mdate_format: '[Date Modified (MDATE): %m/%d/%Y : %H:%M:%S]',
height: 600,
image_caption: true,
quickbars_selection_toolbar: 'bold italic | quicklink h2 h3 blockquote quickimage codesample quicktable',
noneditable_noneditable_class: 'mceNonEditable',
toolbar_mode: 'sliding',
spellchecker_ignore_list: ['Ephox', 'Moxiecode'],
tinycomments_mode: 'embedded',
content_style: '.mymention{ color: gray; }',
contextmenu: 'link image in',
a11y_advanced_options: true,
mentions_selector: '.mymention',
mentions_item_type: 'profile',
codesample_global_prismjs: true
});
tinymce.init({
selector: '#acknowledgement_richtext_field, #content_richtext_field',
plugins: 'print preview importcss tinydrive searchreplace autolink autosave save directionality visualblocks visualchars fullscreen image link media template codesample table charmap hr pagebreak nonbreaking anchor toc insertdatetime advlist lists wordcount imagetools textpattern noneditable help charmap quickbars emoticons blockquote',
menubar: 'file edit view insert format tools table tc help',
toolbar: 'undo redo | bold italic underline strikethrough | fontselect fontsizeselect formatselect | alignleft aligncenter alignright alignjustify | outdent indent | numlist bullist checklist | forecolor backcolor casechange permanentpen formatpainter removeformat | pagebreak | charmap emoticons | fullscreen preview save print | insertfile image media pageembed template link anchor codesample | a11ycheck ltr rtl | showcomments addcomment',
autosave_ask_before_unload: true,
autosave_interval: '30s',
autosave_prefix: '{path}{query}-{id}-',
autosave_restore_when_empty: false,
autosave_retention: '2m',
image_advtab: true,
automatic_uploads: false,
link_list: [
{title: 'My page 1', value: 'https://www.tiny.cloud'},
{title: 'My page 2', value: 'http://www.moxiecode.com'}
],
image_list: [
{title: 'My page 1', value: 'https://www.tiny.cloud'},
{title: 'My page 2', value: 'http://www.moxiecode.com'}
],
image_class_list: [
{title: 'None', value: ''},
{title: 'Some class', value: 'class-name'}
],
importcss_append: true,
templates: [
{
title: 'New Table',
description: 'creates a new table',
content: '<div class="mceTmpl"><table width="98%%" border="0" cellspacing="0" cellpadding="0"><tr><th scope="col"> </th><th scope="col"> </th></tr><tr><td> </td><td> </td></tr></table></div>'
},
{title: 'Starting my story', description: 'A cure for writers block', content: 'Once upon a time...'},
{
title: 'New list with dates',
description: 'New List with dates',
content: '<div class="mceTmpl"><span class="cdate">cdate</span><br /><span class="mdate">mdate</span><h2>My List</h2><ul><li></li><li></li></ul></div>'
}
],
template_cdate_format: '[Date Created (CDATE): %m/%d/%Y : %H:%M:%S]',
template_mdate_format: '[Date Modified (MDATE): %m/%d/%Y : %H:%M:%S]',
height: 600,
image_caption: true,
quickbars_selection_toolbar: 'bold italic | quicklink h2 h3 blockquote quickimage codesample quicktable',
noneditable_noneditable_class: 'mceNonEditable',
toolbar_mode: 'sliding',
spellchecker_ignore_list: ['Ephox', 'Moxiecode'],
tinycomments_mode: 'embedded',
content_style: '.mymention{ color: gray; }',
contextmenu: 'link image in',
a11y_advanced_options: true,
mentions_selector: '.mymention',
mentions_item_type: 'profile',
codesample_global_prismjs: true,
codesample_languages: [
{ text: 'HTML/XML', value: 'markup' },
{ text: 'JavaScript', value: 'javascript' },
{ text: 'TypeScript', value: 'typescript' },
{ text: 'Python', value: 'python' },
{ text: 'Markdown', value: 'markdown' },
{ text: 'LaTeX', value: 'latex' },
{ text: 'CSS', value: 'css' },
{ text: 'PHP', value: 'php' },
{ text: 'Ruby', value: 'ruby' },
{ text: 'Java', value: 'java' },
{ text: 'C', value: 'c' },
{ text: 'C#', value: 'csharp' },
{ text: 'C++', value: 'cpp' },
{ text: 'Rust', value: 'rust' },
{ text: 'Go', value: 'go' },
{ text: 'Scala', value: 'scala' },
{ text: 'SQL', value: 'sql' },
{ text: 'Perl', value: 'perl' },
{ text: 'Swift', value: 'swift' },
{ text: 'Bash', value: 'bash' },
{ text: 'Shell', value: 'shell' },
{ text: 'JSON', value: 'json' },
{ text: 'YAML', value: 'yaml' },

],
});
</script>
{% endblock %}

0 comments on commit 938bea3

Please sign in to comment.