Monthly Rule does not appear on the same day of the week

Hello everyone,

I am not a django expert. On my website I use full calendar and django-scheduler. In the admin area I can enter events under “Custom events”. I created two rules. One rule is weekly and the other rule is monthly. With the “Weekly Rule” it works as it should. With the “Monthly Rule”, the event always appears once a month but always on different days. I would like the event to appear once a month on the same day of the week. The calendar can be seen a little further down on my website. As an example, I entered the event “monthly test”. The problem can be seen very clearly in this example. I’ve already tried to enter parameters for the rule, but that didn’t work.
I would be very happy if someone could help me with a simple and effective solution.

Welcome @maha-man !

To provide you with assistance here, we’re going to need more detailed and specific information about your application. We can start out with you posting your model that is storing the calendar and the view that handles the input for a monthly event.

Note: When posting code here, enclose the code between lines of three backtick - ` characters. This means you’ll have a line of ```, then your code, then another line of ```. This forces the forum software to keep your code properly formatted.

Hi Ken,

thank you very much for your kind answer.

Here are my files:

settings.py

"""
Django settings for mantra_kirtan project.

Generated by 'django-admin startproject' using Django 4.1.7.

For more information on this file, see
https://docs.djangoproject.com/en/4.1/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.1/ref/settings/
"""

from pathlib import Path
import os

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-n5d&_s*s96m6s=w+lb3$mkwb67qibel)f)gj4iw^f_*@i_!2mo'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False

ALLOWED_HOSTS = ['kirtanloveberlin']

# Application definition
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'kirtanloveberlin',
    'schedule',
    #'recurrence', 
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'mantra_kirtan.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'mantra_kirtan.wsgi.application'

# Database
DATABASES = {
    'default': {
		'ENGINE': 'django.db.backends.postgresql_psycopg2',
		'NAME': 'mantra',
		'USER': 'mantra',
		'PASSWORD': 'mantra',
		'HOST': '127.0.0.1',
		'PORT': '5432',
	},
}

# Password validation
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# Internationalization
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'Europe/Berlin' 
USE_I18N = True
USE_TZ = False

# Static files (CSS, JavaScript, Images)


STATIC_URL = '/static/'

#STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "static/")


# Media files (Uploaded Images, Documents, etc.)
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

# Default primary key field type
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

# ...
DEBUG = False
ALLOWED_HOSTS = ['mantra-kirtan.de', 'kirtanloveberlin.com']

admin.py

from django.contrib import admin
from .models import CustomEvent


#from .models import CustomEvent, Termin

# Registriere das ursprüngliche CustomEvent Modell
admin.site.register(CustomEvent)

# # Registriere das neue MeinTermin Modell
# admin.site.register(Termin)

html template

{% load static %}
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Kirtan Love Berlin</title>
    <link href="https://cdn.jsdelivr.net/npm/fullcalendar@5/main.min.css" rel="stylesheet" />
    <script src="https://cdn.jsdelivr.net/npm/fullcalendar@5/main.min.js"></script>
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <style>
        body {
            font-family: 'Arial', sans-serif;
            background-color: #fff3e0;
            color: #5a2d0c;
            line-height: 1.6;
        }
        .container {
            width: 80%;
            margin: auto;
            overflow: hidden;
        }
        #calendar-container, #event-details {
            background: #ffeedd;
            border: 1px solid #ffb380;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
            margin-top: 20px;
        }
        #toggle-calendar {
            background: #ff9e80;
            color: #fff;
            padding: 10px 15px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 16px;
            margin-top: 20px;
        }
        #toggle-calendar:hover {
            background: #ff6e40;
        }
        #footer {
            text-align: center;
            padding: 20px;
            background: #ffb380;
            color: #5a2d0c;
            margin-top: 20px;
        }
        #footer p {
            font-size: 2em;
            margin: 0;
            font-family: 'Courier New', Courier, monospace;
        }
        .introduction-text {
            margin-bottom: 20px;
            background: #ffeedd;
            padding: 20px;
            border-radius: 8px;
        }
        .introduction-text h1 {
            color: #ff9e80;
        }
        .event-image {
            max-width: 100%;
            height: auto;
            padding: 10px 0;
        }
        .bold {
            font-weight: bold;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="introduction-text">
            <h1>Experience the Power of Mantra Meditation!</h1>
            <p>

                <img src="{% static 'images/1.webp' %}" alt="Inspirierendes Bild für Kirtan Love Berlin, das Harmonie und innere Ruhe darstellt" width=100%>
            
                Join us for an <span class="bold">unforgettable afternoon</span> of <span class="bold">soulful and dynamic music</span>, and a
                <span class="bold">pleasant meditation.</span>

                <img src="{% static 'images/2.webp' %}" alt="Inspirierendes Bild für Kirtan Love Berlin, das Harmonie und innere Ruhe darstellt" width=100%>

                The Kirtan will <span class="bold">touch your heart</span> and helps you find <span class="bold">inner peace and harmony</span>.
                       

            </p>
            <img src="{% static 'images/kirtanlove_1.png' %}" alt="Inspirierendes Bild für Kirtan Love Berlin, das Harmonie und innere Ruhe darstellt" width=100%>
            
            <p>
                
                Immerse yourself in the <span class="bold">soothing power of mantras</span>, accompanied by <span class="bold">enchanting melodies</span>
                that will transport you to a state of <span class="bold">pure bliss</span>. 
                
                <img src="{% static 'images/3.webp' %}" alt="Inspirierendes Bild für Kirtan Love Berlin, das Harmonie und innere Ruhe darstellt" width=100%>
                
                This in-person event promises to be a <span class="bold">transformative experience</span>, 
                leaving you <span class="bold">refreshed and inspired</span>. 

            </p>

            <img src="{% static 'images/4.webp' %}" alt="Inspirierendes Bild für Kirtan Love Berlin, das Harmonie und innere Ruhe darstellt" width=100%>

            <h2 class="bold">What can you expect:</h2>
            <ul>
                <li class="list-item">A profound <span class="bold">Mantra Meditation</span> and 
                    <span class="bold">Kirtan</span> – an experience that must be lived to fully 
                    understand its impact.</li>
                <li class="list-item">Exchange in a <span class="bold">warm, welcoming atmosphere</span>.</li>
                <li class="list-item">A <span class="bold">delicious vegetarian meal</span> (vegan options available – please let us know in advance).</li>
            </ul>

            <h2 class="bold">For Whom:</h2>
            <ul>
                <li class="list-item"><span class="bold">Beginners and newcomers</span> are especially welcome! Our events are designed to be accessible and enjoyable for everyone, regardless of experience level.</li>
                <li class="list-item">Those seeking <span class="bold">relaxation, inner peace, and community connection</span> will find a welcoming and supportive atmosphere.</li>
                <li class="list-item">Anyone interested in <span class="bold">exploring meditation, music, and mindful living</span> is invited to join us.</li>
            </ul>
            

            <h2 class="bold">What to Bring:</h2>
            <ul>
                <li class="list-item">No need to bring anything - <span class="bold"> just your presence!</span></li>
            </ul>

        

        <img src="{% static 'images/kirtanlove_2.webp' %}" alt="Inspirierendes Bild für Kirtan Love Berlin, das Harmonie und innere Ruhe darstellt" width=100%>
                
    </div>        
        <button id="toggle-calendar">Find Your Kirtan Event In The Calendar, Click On It AND See Details Below.</button>
        <div id="calendar-container">
            <div id="calendar"></div>
        </div>
        <div id="event-details"></div>

        <div id="footer">
            <p>Kirtan Love Berlin</p>
        </div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', function() {
            var calendarEl = document.getElementById('calendar');
            var calendar = new FullCalendar.Calendar(calendarEl, {
                editable: false,
                timeZone: 'Europe/Berlin',
                events: '/kirtanloveberlin/calendar/',
                eventTimeFormat: {
                    hour: '2-digit',
                    minute: '2-digit',
                    hour12: false
                },
                eventClick: function(info) {
                    var start = info.event.start.toISOString();
                    var end = info.event.end ? info.event.end.toISOString() : start;
                    showEventDetails(info.event.id, start, end);
                }
            });
            calendar.render();

            $('#toggle-calendar').click(function() {
                $('#calendar-container').toggle(); // Ein- und Ausblenden des Kalender-Containers
            });

            // Automatisch das erste Event anzeigen
            setTimeout(() => {
                var firstEvent = calendar.getEvents()[0];
                if (firstEvent) {
                    var start = firstEvent.start.toISOString();
                    var end = firstEvent.end ? firstEvent.end.toISOString() : start;
                    showEventDetails(firstEvent.id, start, end);
                }
            }, 0);

            function loadLatestEventDetails() {
                $.ajax({
                    url: '/latest-event/',
                    method: 'GET',
                    success: function(result) {
                        if (!$.isEmptyObject(result)) {
                            updateEventDetails(result);
                        } else {
                            $('#event-details').html('<p>Keine bevorstehenden Events.</p>');
                        }
                    }
                });
            }

            function showEventDetails(event_id, start, end) {
                $.ajax({
                    url: '/kirtanloveberlin/get_event/' + event_id + '/?date=' + start.split('T')[0],
                    method: 'GET',
                    success: function(result) {
                        updateEventDetails(result);
                    }
                });
            }

            function updateEventDetails(result) {
                var startDate = new Date(result.start);
                var endDate = new Date(result.end);
                $('#event-details').html(`
                    <h2>${result.title}</h2>
                    <p><span class="bold">Start:</span> ${startDate.toLocaleDateString("de-DE")} ${startDate.toLocaleTimeString("de-DE", { hour: '2-digit', minute:'2-digit', hour12: false })}</p>
                    <p><span class="bold">End:</span> ${endDate.toLocaleDateString("de-DE")} ${endDate.toLocaleTimeString("de-DE", { hour: '2-digit', minute:'2-digit', hour12: false })}</p>
                    <p><span class="bold">Description:</span> ${result.description}</p>
                    <p><span class="bold">Comment:</span> ${result.comment}</p>
                    <p><span class="bold">Price:</span> ${result.price}</p>
                    <p><span class="bold">Location:</span> ${result.location}</p>
                    <p><span class="bold">Contact:</span> ${result.contact}</p>
                    <p><span class="bold">Google maps </span> <a href="${result.google_maps_link}">Link</a></p>
                    <p><span class="bold">Website:</span> <a href="${result.website}">${result.website}</a></p>
                    ${result.event_image ? '<img src="'+result.event_image+'" class="event-image">' : ''}
                `);
            }


            loadLatestEventDetails();
        });
    </script>
</body>
</html>

views.py

from django.shortcuts import render, Http404
from django.http import JsonResponse
from django.utils import timezone
from .models import CustomEvent
import json
from datetime import timedelta, datetime
from django.views.decorators.csrf import csrf_exempt

def calendar_view(request):
    start_date = timezone.now()  
    end_date = start_date + timedelta(days=365)

    events = CustomEvent.objects.all()
    event_arr = []
    for event in events:
        occurrences = event.get_occurrences(start_date, end_date)
        for occurrence in occurrences:
            event_sub_arr = {}
            event_sub_arr['id'] = event.id
            event_sub_arr['title'] = event.title
            event_sub_arr['start'] = str(occurrence.start)
            event_sub_arr['end'] = str(occurrence.end)
            event_sub_arr['event_image'] = event.event_image.url if event.event_image else None
            event_sub_arr['google_maps_link'] = event.google_maps_link
            event_arr.append(event_sub_arr)
    return JsonResponse(event_arr, safe=False)

@csrf_exempt
def change_events(request):
    if request.method == 'POST':
        data = json.loads(request.body)
        param = data['param']
        event_id = data['id']
        event = CustomEvent.objects.get(pk=event_id)
        if param == 'start':
            event.start = data['start']
        else:
            event.end = data['end']
        event.save()
    return JsonResponse({}, status=200)

def calendar_page_view(request):
    latest_event = CustomEvent.objects.filter(end__gte=timezone.now()).order_by('end').first()
    if latest_event:
        latest_event_data = {
            'id': latest_event.id,
            'title': latest_event.title,
            'start': str(latest_event.start),
            'end': str(latest_event.end),
            'description': latest_event.description,
            'comment': latest_event.comment,
            'price': latest_event.price,
            'location': latest_event.location,
            'google_maps_link': latest_event.google_maps_link,
            'contact': latest_event.contact,
            'website': latest_event.website,
            'event_image': latest_event.event_image.url if latest_event.event_image else None,
        }
    else:
        latest_event_data = None
    return render(request, 'calendar_page.html', {'latest_event': latest_event_data})

def get_event(request, event_id):
    try:
        event = CustomEvent.objects.get(pk=event_id)
    except CustomEvent.DoesNotExist:
        raise Http404("Event does not exist")

    occurrence_date_str = request.GET.get('date', '')
    start = event.start
    end = event.end
    if occurrence_date_str:
        occurrence_date = datetime.strptime(occurrence_date_str, "%Y-%m-%d").date()
        start = datetime.combine(occurrence_date, start.time())
        end = datetime.combine(occurrence_date, end.time())

    event_data = {
        'id': event.id,
        'title': event.title,
        'start': str(start),
        'end': str(end),
        'description': event.description,
        'comment': event.comment,
        'price': event.price,
        'location': event.location,
        'google_maps_link': event.google_maps_link,
        'contact': event.contact,
        'website': event.website,
        'event_image': event.event_image.url if event.event_image else None,
    }
    return JsonResponse(event_data)

def latest_event_view(request):
    latest_event = CustomEvent.objects.filter(end__gte=timezone.now()).order_by('end').first()

    if latest_event:
        data = {
            'id': latest_event.id,
            'title': latest_event.title,
            'start': str(latest_event.start),
            'end': str(latest_event.end),
            'description': latest_event.description,
            'comment': latest_event.comment,
            'price': latest_event.price,
            'location': latest_event.location,
            'google_maps_link': latest_event.google_maps_link,
            'contact': latest_event.contact,
            'website': latest_event.website,
            'event_image': latest_event.event_image.url if latest_event.event_image else None,
        }
    else:
        data = {}
    return JsonResponse(data)

models.py

from django.db import models
from schedule.models import Event
from django_resized import ResizedImageField
#import recurrence.fields

# class Termin(models.Model):
#     title = models.CharField(max_length=100)
#     start = models.DateTimeField()
#     end = models.DateTimeField()
#     recurrences = recurrence.fields.RecurrenceField()

class CustomEvent(Event):
    event_image = ResizedImageField(size=[500, 300], quality=99, upload_to='event_images', blank=True, null=True)
    comment = models.TextField(blank=True, null=True)
    price = models.CharField(max_length=200, blank=True, null=True)
    location = models.TextField(blank=True, null=True)
    google_maps_link = models.URLField(max_length=200, blank=True, null=True)
    contact = models.TextField(blank=True, null=True)
    website = models.TextField(blank=True, null=True)

    def __str__(self):
        return self.title

Shell I provide more informations?

No, this is enough.

This all looks like it’s very tightly bound to the django-scheduler package. From what I’m seeing, documentation for using this package is rather scarce.

I’m sorry, maybe someone else has used this package and can assist you. I’m not finding enough about this to even begin to offer a suggestion.

Hello Ken,

thanks for your message and your endeavour. No worries. I tried again to find parameters but the result is always not as expected.

I removed the event “monthly test” from my calendar.

I would be happy if someone knows and can help me.

Hello everyone,

I still don’t have a solution to my problem with monthly repeats with django-scheduler package. Is there anyone here who could help me?

Hello Ken,

could you please recommend me a other library then the django-scheduler with which I can create montly, twice a month etc. series in django admin?

When it comes to calendar issues and scheduling, there are no “simple” solutions.

There are enough edge cases like this that I doubt you’re going to find a general purpose package that is going to satisfy your requirements.

You’re talking about making sure that a monthly event occurs on the same day of the week, but a given date only falls on that day 1 - 3 times each year.

So first, you’re going to need to come up with a precise definition that you want the schedule to support. (For example, Microsoft Outlook lets you schedule something for “the second Tuesday of the month”.) Then you can look for a package that supports that type of scheduling - or create your own.

Hi Ken,

thanks for your answer.

I would be very happy if I could enter recurring appointments in my calendar in Django admin with the following rules:

  1. Every week, once a week, always on the same day of the week, for example every week on Friday. This already works with django-scheduler.

  2. Every month, once a month, always on the same day of the week, for example once a month on Sunday. django-scheduler gives me this option in djanga admin, but it doesn’t work because the event always appears in the calendar on a different days of the month and not on the same day of the week.

  3. Every month, twice a month, always on the same day of the week, for example every second Wednesday.

Optionally, it would also be good if I could copy a calendar event in Django admin and then adapt it.

Could you recommend me a packages which can do this things and where also documentations or other informations are available, please?