I'm a beginner and I know there is something I missed but I don't know exactly what, So I have a PySide6 app, and I created a function to generate a calendar in a QTableWidget using calendar module in python
all worked fine but the problem came when I tried to add a navigation buttons to get next and previous month:
This is my function:
import sys
import os
import platform
import datetime as dt
import time
import calendar
from PySide6 import *
from PySide6 import QtGui
from PySide6 import QtWidgets
from PySide6 import QtCore
from PySide6.QtGui import QColor
from functools import partial
yy = int(dt.datetime.now().strftime("%Y"))
mm = int(dt.datetime.now().strftime("%m"))
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
global widgets
widgets = self.ui
# Calender generator
self.calender_gen(mm,yy)
def calender_gen(self, mm_g, yy_g):
# Creat table rows and columns
widgets.tableWidget_3.setRowCount(5)
widgets.tableWidget_3.setColumnCount(7)
# Table header labels
week_list = ["Sat","Sun","Mon","Tue","Wed","Thu","Fri"]
widgets.tableWidget_3.setHorizontalHeaderLabels(week_list)
# Start inserting days of the month into the table
row = 0
col = 0
for week in calendar.monthcalendar(yy_g,mm_g):
for day in week:
if day == 0:
widgets.tableWidget_3.setItem(row,col,QTableWidgetItem(" "))
else:
widgets.tableWidget_3.setItem(row,col,QTableWidgetItem(str(day)))
col += 1
row += 1
col = 0
print(mm_g,yy_g)
# Connect Buttons to function
widgets.pushButton_3.clicked.connect(partial(self.next_calendar_butt,mm_g,yy_g))
widgets.pushButton_2.clicked.connect(partial(self.prev_calendar_butt,mm_g,yy_g))
def next_calendar_butt(self,mm_new, yy_new):
mm_new += 1
if mm_new > 12:
mm_new = 1
yy_new += 1
widgets.tableWidget_3.setRowCount(0)
widgets.tableWidget_3.setColumnCount(0)
self.calender_gen(mm_new,yy_new)
def prev_calendar_butt(self,mm_g_new,yy_g_new):
mm_g_new -= 1
if mm_g_new == 0:
mm_g_new = 12
yy_g_new -= 1
widgets.tableWidget_3.setRowCount(0)
widgets.tableWidget_3.setColumnCount(0)
self.calender_gen(mm_g_new,yy_g_new)
When I run the app the calendar shows in the table as in the image image of the GUI table
The Console output Console prints 11 2021
When I click on pushButton_3 for first time click it works normally and prints '12 2021' in the console console prints 12 2021
But after I click on the same button again it starts duplication: console prints '12 2021 1 2022' console prints 12 2021 1 2022
If I click again it prints '12 2021 1 2022 1 2022 2 2022' as in the image enter image description here with every click it duplicates more where it should only print one statement i.e '2 2022'
I tried to move the below lines out of the calendar_gen() function but I coudn't deliver the parameters, even after declaring global variables and assign them to the parameters :
widgets.pushButton_3.clicked.connect(partial(self.next_calendar_butt,mm_g,yy_g))
widgets.pushButton_2.clicked.connect(partial(self.prev_calendar_butt,mm_g,yy_g))
I've tried to do this:
Inside the calendar_gen() function I declared global variables and assigned them to the function parameters in order to create something like a global parameter
global var_mm
global var_yy
var_mm = mm_g
var_yy = yy_g
then in the init(self) function I put those 2 lines:
def __init__(self):
widgets.pushButton_3.clicked.connect(partial(self.next_calendar_butt,var_mm,var_yy))
widgets.pushButton_2.clicked.connect(partial(self.prev_calendar_butt,var_mm,var_yy))
But this didn't work in the console it prints '11 2021' when I run the app then when I click on pushButton_3 it prints '12 2021' and when I click again on it, it prints '12 2021' again and so on
Same with the other button it prints '10 2021' again and again
Qt signal connections are not exclusive (by default), and a signal can be connected to the same function more than once.
Since you're connecting the clicked signals of the buttons in calender_gen, everytime that function is called you're adding a further connection to those signals. The result is that the connected functions will be called as many time as they have been connected every time the signal is emitted.
A proper solution is to connect to the functions that would switch the month and keep a reference to the current month for the "new" month computation.
Since the functions are almost identical, it's better to group them in a unique function, and then connect the signals to separate functions that would eventually call that former function with an appropriate parameter:
class MainWindow(QMainWindow):
def __init__(self):
# ...
self.calender_gen(mm,yy)
self.pushButton_2.clicked.connect(self.prev_month)
self.pushButton_3.clicked.connect(self.next_month)
def calender_gen(self, mm_g, yy_g):
# Creat table rows and columns
self.tableWidget_3.setRowCount(5)
self.tableWidget_3.setColumnCount(7)
# Table header labels
week_list = ["Sat","Sun","Mon","Tue","Wed","Thu","Fri"]
self.tableWidget_3.setHorizontalHeaderLabels(week_list)
# Start inserting days of the month into the table
row = 0
col = 0
for week in calendar.monthcalendar(yy_g,mm_g):
for day in week:
if day == 0:
self.tableWidget_3.setItem(row,col,QTableWidgetItem(" "))
else:
self.tableWidget_3.setItem(row,col,QTableWidgetItem(str(day)))
col += 1
row += 1
col = 0
self.current_month = mm_g
self.current_year = yy_g
def prev_month(self):
self.step_month(-1)
def next_month(self):
self.step_month(1)
def step_month(self, delta):
mm_new = self.current_month + delta
mm_year = self.current_year
if mm_new > 12:
mm_new = 1
mm_year += 1
elif mm_new < 1:
mm_new = 12
mm_year -= 1
self.calender_gen(mm_new, mm_year)
Obviously, properly implementing QCalendarWidget might be much simpler, as it already provides most of the functionalities.
i want to implement an event calendar. i am faced with the problem of displaying the closest event to today's date. to find the nearest date, i use __gte in queryset, after queryset finds all the nearest dates, I want to highlight the first one with a different color here is my solution could you tell me what i'm doing wrong?
This is my Model
class Events(models.Model):
title = models.CharField(max_length=100)
slug = models.SlugField()
start_time = models.DateTimeField()
end_time = models.DateTimeField()
def __str__(self):
return self.title
#property
def get_html_url(self):
url = reverse('cal:events', args=(self.slug,))
return f'<a href="{url}">'
And my HTMLCalendar
from datetime import datetime, timedelta
from calendar import HTMLCalendar
from .models import Events
class Calendar(HTMLCalendar):
def __init__(self, year=None, month=datetime.now().month):
self.year = year
self.month = month
super(Calendar, self).__init__()
# formats a day as a td
# filter events by day
def formatday(self, day, events):
events_per_day = events.filter(start_time__day=day)
d = ''
if Events.objects.filter(start_time__day=day, start_time__month=self.month).exists():
for event in events_per_day:
d += f'{event.get_html_url}'
if day != 0:
ev = Events.objects.filter(start_time__gt=datetime.now()).first()
if ev:
return f"<td>{d}<span style='color:red;' class='date'>{day}</span></a></td>"
else:
return f"<td>{d}<span style='color:aliceblue;' class='date'>{day}</span></a></td>"
return '<td></td>'
else:
if day != 0:
return f"<td><b><span class='date'>{day}</span> </b></td>"
return '<td></td>'
# formats a week as a tr
def formatweek(self, theweek, events):
week = ''
for d, weekday in theweek:
week += self.formatday(d, events)
return f'<tr> {week} </tr>'
# formats a month as a table
# filter events by year and month
def formatmonth(self, withyear=True, ):
events = Events.objects.filter(start_time__year=self.year, start_time__month=self.month)
cal = f'<table border="0" cellpadding="0" cellspacing="0" class="calendar">\n'
cal += f'{self.formatmonthname(self.year, self.month, withyear=withyear)}\n'
cal += f'{self.formatweekheader()}\n'
for week in self.monthdays2calendar(self.year, self.month):
cal += f'{self.formatweek(week, events)}\n'
return cal
my solution is in the format day () function I am executing a query and if there is the first element I want to highlight it in red and paint over all the others with a different color
I'd consider moving the formatting to your template. You could then serve the first variable with the red formating and loop through the remaining values with the other formatting choice.
Use the view to generate the data you'll need in the template in the order you'd like to use it. Handle the presentation in the template.
I'm looking for a library in Python which will provide at and cron like functionality.
I'd quite like have a pure Python solution, rather than relying on tools installed on the box; this way I run on machines with no cron.
For those unfamiliar with cron: you can schedule tasks based upon an expression like:
0 2 * * 7 /usr/bin/run-backup # run the backups at 0200 on Every Sunday
0 9-17/2 * * 1-5 /usr/bin/purge-temps # run the purge temps command, every 2 hours between 9am and 5pm on Mondays to Fridays.
The cron time expression syntax is less important, but I would like to have something with this sort of flexibility.
If there isn't something that does this for me out-the-box, any suggestions for the building blocks to make something like this would be gratefully received.
Edit
I'm not interested in launching processes, just "jobs" also written in Python - python functions. By necessity I think this would be a different thread, but not in a different process.
To this end, I'm looking for the expressivity of the cron time expression, but in Python.
Cron has been around for years, but I'm trying to be as portable as possible. I cannot rely on its presence.
If you're looking for something lightweight checkout schedule:
import schedule
import time
def job():
print("I'm working...")
schedule.every(10).minutes.do(job)
schedule.every().hour.do(job)
schedule.every().day.at("10:30").do(job)
while 1:
schedule.run_pending()
time.sleep(1)
Disclosure: I'm the author of that library.
You could just use normal Python argument passing syntax to specify your crontab. For example, suppose we define an Event class as below:
from datetime import datetime, timedelta
import time
# Some utility classes / functions first
class AllMatch(set):
"""Universal set - match everything"""
def __contains__(self, item): return True
allMatch = AllMatch()
def conv_to_set(obj): # Allow single integer to be provided
if isinstance(obj, (int,long)):
return set([obj]) # Single item
if not isinstance(obj, set):
obj = set(obj)
return obj
# The actual Event class
class Event(object):
def __init__(self, action, min=allMatch, hour=allMatch,
day=allMatch, month=allMatch, dow=allMatch,
args=(), kwargs={}):
self.mins = conv_to_set(min)
self.hours= conv_to_set(hour)
self.days = conv_to_set(day)
self.months = conv_to_set(month)
self.dow = conv_to_set(dow)
self.action = action
self.args = args
self.kwargs = kwargs
def matchtime(self, t):
"""Return True if this event should trigger at the specified datetime"""
return ((t.minute in self.mins) and
(t.hour in self.hours) and
(t.day in self.days) and
(t.month in self.months) and
(t.weekday() in self.dow))
def check(self, t):
if self.matchtime(t):
self.action(*self.args, **self.kwargs)
(Note: Not thoroughly tested)
Then your CronTab can be specified in normal python syntax as:
c = CronTab(
Event(perform_backup, 0, 2, dow=6 ),
Event(purge_temps, 0, range(9,18,2), dow=range(0,5))
)
This way you get the full power of Python's argument mechanics (mixing positional and keyword args, and can use symbolic names for names of weeks and months)
The CronTab class would be defined as simply sleeping in minute increments, and calling check() on each event. (There are probably some subtleties with daylight savings time / timezones to be wary of though). Here's a quick implementation:
class CronTab(object):
def __init__(self, *events):
self.events = events
def run(self):
t=datetime(*datetime.now().timetuple()[:5])
while 1:
for e in self.events:
e.check(t)
t += timedelta(minutes=1)
while datetime.now() < t:
time.sleep((t - datetime.now()).seconds)
A few things to note: Python's weekdays / months are zero indexed (unlike cron), and that range excludes the last element, hence syntax like "1-5" becomes range(0,5) - ie [0,1,2,3,4]. If you prefer cron syntax, parsing it shouldn't be too difficult however.
More or less same as above but concurrent using gevent :)
"""Gevent based crontab implementation"""
from datetime import datetime, timedelta
import gevent
# Some utility classes / functions first
def conv_to_set(obj):
"""Converts to set allowing single integer to be provided"""
if isinstance(obj, (int, long)):
return set([obj]) # Single item
if not isinstance(obj, set):
obj = set(obj)
return obj
class AllMatch(set):
"""Universal set - match everything"""
def __contains__(self, item):
return True
allMatch = AllMatch()
class Event(object):
"""The Actual Event Class"""
def __init__(self, action, minute=allMatch, hour=allMatch,
day=allMatch, month=allMatch, daysofweek=allMatch,
args=(), kwargs={}):
self.mins = conv_to_set(minute)
self.hours = conv_to_set(hour)
self.days = conv_to_set(day)
self.months = conv_to_set(month)
self.daysofweek = conv_to_set(daysofweek)
self.action = action
self.args = args
self.kwargs = kwargs
def matchtime(self, t1):
"""Return True if this event should trigger at the specified datetime"""
return ((t1.minute in self.mins) and
(t1.hour in self.hours) and
(t1.day in self.days) and
(t1.month in self.months) and
(t1.weekday() in self.daysofweek))
def check(self, t):
"""Check and run action if needed"""
if self.matchtime(t):
self.action(*self.args, **self.kwargs)
class CronTab(object):
"""The crontab implementation"""
def __init__(self, *events):
self.events = events
def _check(self):
"""Check all events in separate greenlets"""
t1 = datetime(*datetime.now().timetuple()[:5])
for event in self.events:
gevent.spawn(event.check, t1)
t1 += timedelta(minutes=1)
s1 = (t1 - datetime.now()).seconds + 1
print "Checking again in %s seconds" % s1
job = gevent.spawn_later(s1, self._check)
def run(self):
"""Run the cron forever"""
self._check()
while True:
gevent.sleep(60)
import os
def test_task():
"""Just an example that sends a bell and asd to all terminals"""
os.system('echo asd | wall')
cron = CronTab(
Event(test_task, 22, 1 ),
Event(test_task, 0, range(9,18,2), daysofweek=range(0,5)),
)
cron.run()
None of the listed solutions even attempt to parse a complex cron schedule string. So, here is my version, using croniter. Basic gist:
schedule = "*/5 * * * *" # Run every five minutes
nextRunTime = getNextCronRunTime(schedule)
while True:
roundedDownTime = roundDownTime()
if (roundedDownTime == nextRunTime):
####################################
### Do your periodic thing here. ###
####################################
nextRunTime = getNextCronRunTime(schedule)
elif (roundedDownTime > nextRunTime):
# We missed an execution. Error. Re initialize.
nextRunTime = getNextCronRunTime(schedule)
sleepTillTopOfNextMinute()
Helper routines:
from croniter import croniter
from datetime import datetime, timedelta
# Round time down to the top of the previous minute
def roundDownTime(dt=None, dateDelta=timedelta(minutes=1)):
roundTo = dateDelta.total_seconds()
if dt == None : dt = datetime.now()
seconds = (dt - dt.min).seconds
rounding = (seconds+roundTo/2) // roundTo * roundTo
return dt + timedelta(0,rounding-seconds,-dt.microsecond)
# Get next run time from now, based on schedule specified by cron string
def getNextCronRunTime(schedule):
return croniter(schedule, datetime.now()).get_next(datetime)
# Sleep till the top of the next minute
def sleepTillTopOfNextMinute():
t = datetime.utcnow()
sleeptime = 60 - (t.second + t.microsecond/1000000.0)
time.sleep(sleeptime)
I know there are a lot of answers, but another solution could be to go with decorators. This is an example to repeat a function everyday at a specific time. The cool think about using this way is that you only need to add the Syntactic Sugar to the function you want to schedule:
#repeatEveryDay(hour=6, minutes=30)
def sayHello(name):
print(f"Hello {name}")
sayHello("Bob") # Now this function will be invoked every day at 6.30 a.m
And the decorator will look like:
def repeatEveryDay(hour, minutes=0, seconds=0):
"""
Decorator that will run the decorated function everyday at that hour, minutes and seconds.
:param hour: 0-24
:param minutes: 0-60 (Optional)
:param seconds: 0-60 (Optional)
"""
def decoratorRepeat(func):
#functools.wraps(func)
def wrapperRepeat(*args, **kwargs):
def getLocalTime():
return datetime.datetime.fromtimestamp(time.mktime(time.localtime()))
# Get the datetime of the first function call
td = datetime.timedelta(seconds=15)
if wrapperRepeat.nextSent == None:
now = getLocalTime()
wrapperRepeat.nextSent = datetime.datetime(now.year, now.month, now.day, hour, minutes, seconds)
if wrapperRepeat.nextSent < now:
wrapperRepeat.nextSent += td
# Waiting till next day
while getLocalTime() < wrapperRepeat.nextSent:
time.sleep(1)
# Call the function
func(*args, **kwargs)
# Get the datetime of the next function call
wrapperRepeat.nextSent += td
wrapperRepeat(*args, **kwargs)
wrapperRepeat.nextSent = None
return wrapperRepeat
return decoratorRepeat
I like how the pycron package solves this problem.
import pycron
import time
while True:
if pycron.is_now('0 2 * * 0'): # True Every Sunday at 02:00
print('running backup')
time.sleep(60) # The process should take at least 60 sec
# to avoid running twice in one minute
else:
time.sleep(15) # Check again in 15 seconds
There isn't a "pure python" way to do this because some other process would have to launch python in order to run your solution. Every platform will have one or twenty different ways to launch processes and monitor their progress. On unix platforms, cron is the old standard. On Mac OS X there is also launchd, which combines cron-like launching with watchdog functionality that can keep your process alive if that's what you want. Once python is running, then you can use the sched module to schedule tasks.
Another trivial solution would be:
from aqcron import At
from time import sleep
from datetime import datetime
# Event scheduling
event_1 = At( second=5 )
event_2 = At( second=[0,20,40] )
while True:
now = datetime.now()
# Event check
if now in event_1: print "event_1"
if now in event_2: print "event_2"
sleep(1)
And the class aqcron.At is:
# aqcron.py
class At(object):
def __init__(self, year=None, month=None,
day=None, weekday=None,
hour=None, minute=None,
second=None):
loc = locals()
loc.pop("self")
self.at = dict((k, v) for k, v in loc.iteritems() if v != None)
def __contains__(self, now):
for k in self.at.keys():
try:
if not getattr(now, k) in self.at[k]: return False
except TypeError:
if self.at[k] != getattr(now, k): return False
return True
I don't know if something like that already exists. It would be easy to write your own with time, datetime and/or calendar modules, see http://docs.python.org/library/time.html
The only concern for a python solution is that your job needs to be always running and possibly be automatically "resurrected" after a reboot, something for which you do need to rely on system dependent solutions.
I need help for my python scripts. How to access my clock's function through Date's classes ?
from datetime import date
from datetime import datetime
class Date(object):
def date_today(self):
now = date.today()
print (now)
class Time(Date):
pass
def clock(self):
hr = datetime.now()
hr_now = hr.hour
print (hr_now)
cr_date = Date()
print (cr_date.date_today())
print (cr_date.date_today.clock())
i got an error --> AttributeError: 'function' object has no attribute 'clock'. What is the reason for this error?
you can also add minute, second and other related functions in your time class. I hope it will help.
from datetime import date
from datetime import datetime
class Time():
def clock(self):
hr = datetime.now()
hr_now = hr.hour
return hr_now
class Date():
def __init__(self):
self.time = Time()
def date_today(self):
now = date.today()
return now
def clock(self):
return self.time.clock()
cr_date = Date()
print(cr_date.date_today())
print(cr_date.clock())
I'm working on a calendar via Python's HTMLCalendar and Django. The functions I'm using to input data into the calendar are showing up as unbound, and therefore not working.
Here's the calendar code:
from www.wednesday.models import Event
import calendar
e = Event()
class EventCal(calendar.HTMLCalendar):
def formatday(self, day, weekday):
if day == 0:
return '<td class="noday"> </td>' # Day outside month
if day == e.dd():
return '<td class="%s">%d</p>%s</td>' % (self.cssclasses[weekday], day, e.link(), e.res())
else:
return '<td class="%s">%d</td>' % (self.cssclasses[weekday], day)
class rendCal:
c = EventCal(calendar.SUNDAY)
Here's my models.py:
from django.db import models
class Event(models.Model):
Restaurant = models.CharField(max_length=200)
LinkURL = models.CharField(max_length=200)
created = models.DateTimeField(auto_now_add=True)
DateDay = models.IntegerField(max_length=2)
def dd(self):
return '%i' % self.DateDay
def link(self):
return '%s' % self.LinkURL
def res(self):
return '%s' % self.Restaurant
And lastly, my views.py:
from django.shortcuts import render_to_response
import www.wednesday.models
from www.wednesday.cal import rendCal
import datetime as dt
def calendar(request):
now = dt.datetime.now()
cal = rendCal.c.formatmonth(now.year, now.month)
return render_to_response('cal.html', {'calendar': cal})
Everything works except for the functions from Event that are called inside the EventCal class.
Obviously I'm quite new at this.
Okay, #Marcin asked for an error, this is what I'm seeing, also I corrected the capitalization.
TypeError at /calendar/
unbound method dd() must be called with Event instance as first argument (got nothing instead)
cal.py in formatday, line 9
The environment variables in EventCal from Event are showing up blank, I'm pretty sure that's why I'd been getting the needs int not str error. When I change e.dd() to a static number, it returns everything but e.link() and e.res().
dd() is a method of an instance of the class.
You call it like this:
e = Event()
x = e.dd()
You can't apply dd to Event itself.
I am not sure what exactly you are trying to do, so I am not sure how you need to modify your code.
In formatday you have:
if day == 0:
but also:
if day == Event.dd():
and Event.dd() returns a string.
So, is day an int or a string?