Browse Source

feat: calender dobodi

master
alireza 1 year ago
parent
commit
7d4d119294
  1. 134
      apps/dobodbi_calendar/admin.py
  2. 63
      apps/dobodbi_calendar/admin/calendar.html
  3. 2
      apps/dobodbi_calendar/admin/json_date_field.html
  4. 31
      apps/dobodbi_calendar/migrations/0001_initial.py
  5. 11
      apps/dobodbi_calendar/models.py
  6. 34
      apps/dobodbi_calendar/serializer.py
  7. 9
      apps/dobodbi_calendar/urls.py
  8. 63
      apps/dobodbi_calendar/views.py
  9. 16
      config/settings/base.py
  10. 1
      config/urls.py
  11. 14
      dynamic_preferences/dynamic_preferences_registry.py
  12. 13
      utils/config_getter.py
  13. 18
      utils/schema.py

134
apps/dobodbi_calendar/admin.py

@ -1,3 +1,135 @@
import json
from django.contrib import admin
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.utils.safestring import mark_safe
from django.utils.html import format_html
from dj_language.models import Language
from django import forms
from import_export import fields, widgets, resources
from import_export.admin import ImportExportModelAdmin
from unfold.decorators import display
from unfold.admin import ModelAdmin, TabularInline
from unfold.forms import AdminPasswordChangeForm, UserChangeForm, UserCreationForm
from unfold.contrib.filters.admin import (
RangeDateFilter,
RangeNumericFilter,
SingleNumericFilter,
ChoicesDropdownFilter
)
from apps.dobodbi_calendar.models import CalendarOccasions
from utils.json_editor_field import JsonEditorWidget
from utils.admin import project_admin_site
from utils.schema import get_calender_dates_schema
# Register your models here.
class CalendarOccasionsForm(forms.ModelForm):
class Meta:
model = CalendarOccasions
fields = '__all__'
widgets = {
'dates': JsonEditorWidget(attrs={
'schema': get_calender_dates_schema(),
'title': _('Dates'),
}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
class CalendarOccasionsAdmin(ModelAdmin):
form = CalendarOccasionsForm
ordering = ('-id',)
list_display = [
"title",
"display_occasion_type",
"display_event_type",
"is_global",
"is_yearly",
"display_dates",
]
list_filter = [
("occasion_type", ChoicesDropdownFilter),
("event_type", ChoicesDropdownFilter),
"is_global",
"is_yearly",
("created_at", RangeDateFilter),
]
search_fields = [
"title",
"dates",
]
fieldsets = (
("Basic Information", {
"fields": (
"title",
"is_global",
"is_yearly",
"occasion_type",
"event_type",
),
"description": "Main information about the calendar occasion",
}),
("Dates Configuration", {
"fields": ("dates",),
"classes": ("collapse",),
"description": "Configure dates for this occasion",
}),
("Metadata", {
"fields": ("created_at", "updated_at"),
"classes": ("collapse",),
"description": "Metadata information",
}),
)
readonly_fields = ["created_at", "updated_at"]
# سفارشی‌سازی اکشن‌ها
actions = ["make_global", "make_not_global"]
# متدهای اکشن
@admin.action(description="Mark selected occasions as global")
def make_global(self, request, queryset):
queryset.update(is_global=True)
@admin.action(description="Mark selected occasions as not global")
def make_not_global(self, request, queryset):
queryset.update(is_global=False)
@display(description="Occasion Type", label=True)
def display_occasion_type(self, obj):
return obj.get_occasion_type_display()
@display(description="Event Type", label=True)
def display_event_type(self, obj):
return obj.get_event_type_display()
@display(description="Dates")
def display_dates(self, obj):
from django.utils.html import format_html
return "\n".join([f"{i['month']}/{i['day']}" for i in obj.dates])
def get_search_results(self, request, queryset, search_term):
queryset, use_distinct = super().get_search_results(request, queryset, search_term)
try:
# امکان جستجو در فیلد JSON
import json
json.loads(search_term)
queryset |= self.model.objects.filter(dates__contains=search_term)
except ValueError:
pass
return queryset, use_distinct
project_admin_site.register(CalendarOccasions, CalendarOccasionsAdmin)

63
apps/dobodbi_calendar/admin/calendar.html

@ -0,0 +1,63 @@
{% extends 'admin/change_form.html' %}
{% block scripts %}
{{ block.super }}
<style>
.json-view-editor .row .form-group {
display: flex;
align-items: baseline;
}
.json-view-editor .row .form-group label {
margin-bottom: 0;
margin-right: 10px;
}
</style>
<script>
let editor = document.getElementById('id_dates')
let schema_str = window.sc = JSON.parse(editor.value)
$(editor).addClass("hidden")
let json_viewer_div = $(`<div class="json-view-editor" id='date-view-editor'></div>`)
$(editor).parent().append(json_viewer_div)
function init(start_value = []) {
if (window.jsoneditor) {
window.jsoneditor.destroy()
}
window.jsoneditor = new JSONEditor(
json_viewer_div[0], {
theme: 'bootstrap4',
schema: {
type: "array",
format: 'table',
title: ' ',
items: {
type: 'object',
title: 'date',
properties: {
year: {type: 'string', format: 'number'},
month: {type: 'string', format: 'number'},
day: {type: 'string', format: 'number'},
}
},
},
disable_edit_json: true,
disable_properties: true,
disable_array_delete_all_rows: true,
disable_array_delete_last_row: true,
disable_array_reorder: true,
grid_columns: 3,
prompt_before_delete: false,
disable_collapse: true,
startval: start_value
})
window.jsoneditor.on('change', () => {
$(editor).val(JSON.stringify(jsoneditor.getValue()))
})
}
init(schema_str || [])
</script>
{% endblock %}

2
apps/dobodbi_calendar/admin/json_date_field.html

@ -0,0 +1,2 @@
<textarea name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>
{% if widget.value %}{{ widget.value }}{% endif %}</textarea>

31
apps/dobodbi_calendar/migrations/0001_initial.py

@ -0,0 +1,31 @@
# Generated by Django 5.1.8 on 2025-05-04 08:31
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='CalendarOccasions',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255, verbose_name='title')),
('is_global', models.BooleanField(default=False, help_text='check this field if event is global', verbose_name='is global')),
('occasion_type', models.CharField(choices=[('georgian', 'georgian'), ('lunar', 'lunar')], default='georgian', help_text='Choose between georgian or lunar. default to georgian', max_length=12, verbose_name='occasion type')),
('dates', models.JSONField(verbose_name='dates')),
('is_yearly', models.BooleanField(default=True, help_text='check this field if event is annually', verbose_name='is yearly')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='updated at')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')),
('event_type', models.CharField(choices=[('national', 'National'), ('international', 'International'), ('religious', 'Religious')], max_length=16, null=True, verbose_name='event type')),
],
options={
'ordering': ('-updated_at',),
},
),
]

11
apps/dobodbi_calendar/models.py

@ -1,6 +1,6 @@
from django.db import models
# Create your models here.
from django.utils.translation import gettext_lazy as _
class CalendarOccasions(models.Model):
@ -36,4 +36,13 @@ class CalendarOccasions(models.Model):
help_text=_('check this field if event is annually')
)
updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at'))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at'))
event_type = models.CharField(max_length=16, choices=EventType.choices, null=True, verbose_name=_('event type'))
class Meta:
ordering = ('-updated_at',)
def __str__(self) -> str:
return self.title

34
apps/dobodbi_calendar/serializer.py

@ -0,0 +1,34 @@
from rest_framework import serializers
from apps.dobodbi_calendar.models import CalendarOccasions
class CalendarSerializer(serializers.ModelSerializer):
type = serializers.CharField(source='occasion_type')
dates = serializers.SerializerMethodField()
# def get_countries(self, obj):
# if not obj.countries or obj.countries[0] == 'ALL':
# return ["All"]
# return [country.name or country.code for country in obj.countries]
# def get_holiday_in_countries(self, obj):
# return [country.name or country.code for country in obj.holiday_in_countries]
def get_dates(self, obj):
dates = []
for date in obj.dates:
dates.append({
'day': str(date['day']),
'month': str(date['month']),
'year': str(date.get('year', '')),
})
return dates
class Meta:
model = CalendarOccasions
fields = ('id', 'title', 'type', 'event_type', 'dates', 'is_yearly',)

9
apps/dobodbi_calendar/urls.py

@ -0,0 +1,9 @@
from django.urls import path
from apps.dobodbi_calendar.views import CalendarList, AdjustmentConfigView
urlpatterns = [
path('occasions/', CalendarList.as_view()),
path('adjustemnts/', AdjustmentConfigView.as_view()),
]

63
apps/dobodbi_calendar/views.py

@ -1,3 +1,64 @@
from django.shortcuts import render
# Create your views here.
import datetime
import json
from collections import OrderedDict
from django.db.models import Q
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from rest_framework.generics import ListAPIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView, status
from apps.account.models import User
from apps.dobodbi_calendar.models import CalendarOccasions
from apps.dobodbi_calendar.serializer import CalendarSerializer
from utils.config_getter import get_config
class CalendarList(ListAPIView):
serializer_class = CalendarSerializer
pagination_class = None
permission_classes = (IsAuthenticated,)
# @method_decorator(cache_page(60 * 15)) # Cache for 1 Hour
# def dispatch(self, *args, **kwargs):
# return super().dispatch(*args, **kwargs)
def get_queryset(self):
queryset = CalendarOccasions.objects.all()
req = self.request
if v := req.query_params.get('last_updated'):
query &= Q(
updated_at__gte=v
)
return queryset
def list(self, request, *args, **kwargs):
q = self.get_queryset()
last_item_date = q.first()
if last_item_date:
last_updated = last_item_date.updated_at + datetime.timedelta(microseconds=1)
last_updated = str(last_updated)
else:
last_updated = None
d = self.get_serializer(q, many=True).data
data = OrderedDict({
'last_updated': last_updated,
'total': len(d),
'data': d,
})
return Response(data)
class AdjustmentConfigView(APIView):
def get(self, request):
adjustment_config = get_config('calendar__Adjustment')
return Response(json.loads(adjustment_config))

16
config/settings/base.py

@ -52,6 +52,7 @@ LOCAL_APPS = [
'apps.library.apps.LibraryConfig',
'apps.video.apps.VideoConfig',
'apps.bookmark.apps.BookmarkConfig',
'apps.dobodbi_calendar.apps.DobodbiCalendarConfig',
'dynamic_preferences',
]
@ -555,10 +556,21 @@ UNFOLD = {
]
},
{
"title": _(""),
"items": [
{
"title": _("Calender"),
"icon": "calendar_today",
"link": reverse_lazy("admin:dobodbi_calendar_calendaroccasions_changelist"),
"permission": lambda request: request.user.is_staff,
},
],
},
{
"title": _("Courses"),
"collapsible": True,
# "separator": True,
"separator": True,
"items": [
{
"title": _("Categories"),
@ -595,7 +607,7 @@ UNFOLD = {
{
"title": _("Transactions"),
"collapsible": True,
# "separator": True,
"separator": True,
"items": [
{
"title": _("Transactions"),

1
config/urls.py

@ -72,6 +72,7 @@ api_patterns = [
path('library/', include('apps.library.urls')),
path('videos/', include('apps.video.urls')),
path('bookmarks/', include('apps.bookmark.urls')),
path('calendar/', include('apps.dobodbi_calendar.urls')),
path('settings/', include('dynamic_preferences.urls')),

14
dynamic_preferences/dynamic_preferences_registry.py

@ -10,12 +10,15 @@ from dynamic_preferences.registries import global_preferences_registry
from dynamic_preferences.types import BasePreferenceType, BaseSerializer, LongStringPreference, StringPreference, \
FilePreference
from utils.json_editor_field import JsonEditorWidget
from unfold.contrib.forms.widgets import WysiwygWidget
from unfold.contrib.forms.widgets import WysiwygWidget, ArrayWidget
from unfold.widgets import UnfoldAdminTextareaWidget
class EditorPreferences(LongStringPreference):
widget = WysiwygWidget(attrs={'class': 'editor-field'})
class EditorTextPreferences(LongStringPreference):
widget = UnfoldAdminTextareaWidget(attrs={'class': 'editor-field', 'rows': 20})
@global_preferences_registry.register
class AboutUsConfig(EditorPreferences):
@ -181,3 +184,12 @@ class SupportConfig(JsonFieldCard):
verbose_name = 'Card Detail'
default = {}
@global_preferences_registry.register
class CalendarAdjustmentConfig(EditorTextPreferences):
section = Section('calendar', verbose_name='CalendarAdjustmentConfig')
name = 'Adjustment'
required = False
verbose_name = 'Calendar Adjustment Config'
default = ''

13
utils/config_getter.py

@ -0,0 +1,13 @@
import logging
from dynamic_preferences.registries import global_preferences_registry
global_preferences = global_preferences_registry.manager()
def get_config(key):
try:
return global_preferences[key]
except Exception as e:
logging.error(f"error gettings config {key}: {e}")
return None

18
utils/schema.py

@ -36,7 +36,6 @@ def get_weekly_timing_schema():
}
def get_course_feature_schema():
return {
'type': "array",
@ -50,3 +49,20 @@ def get_course_feature_schema():
}
}
}
def get_calender_dates_schema():
return {
'type': "array",
'format': 'table',
'title': ' ',
'items': {
'type': 'object',
'title': str(_('')),
'properties': {
'year': {'type': 'string', 'format': 'number', 'title': str(_('year'))},
'month': {'type': 'string', 'format': 'number', 'title': str(_('month'))},
'day': {'type': 'string', 'format': 'number', 'title': str(_('day'))},
}
}
}
Loading…
Cancel
Save