I want to update a value every day at a certain time later.
I wrote my models and views look like
models.py
class InvestmentRequest(models.Model):
user = models.OneToOneField(User, related_name=“investment_request”, on_delete=models.CASCADE)
profit = models.DecimalField(max_digits=15, decimal_places=2)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.user.username}-{self.amount}"
def increase_balance(self, profit ):
self.profit += decimal.Decimal(profit )
self.save()
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
views.py
from apscheduler.schedulers.background import BackgroundScheduler
def roi_profit(request):
investment = InvestmentRequest.objects.get(user=request.user)
amount = 15
investment .increase_balance(amount)
return …
scheduler = BackgroundScheduler()
job = None
def tick():
print(‘One tick!’)
# roi_profit() --- any way like this
def start_job():
global job
job = scheduler.add_job(tick, 'interval', seconds=3)
try:
scheduler.start()
except:
pass
I used here “appscheduler” library because my requirement is minimal, it’s not a complex configuration like celery.
Is there any way to run ‘roi_profit’ views at a certain time every day? Here ‘tick’ function runs very smoothly because it is a normal function. But I can’t call the views function because it’s a required request. And without request, I can’t get any value from request.user. It’s necessary. So this way how I can update my model value at a certain time later every day. Over a couple of days I’m struggling with it but couldn’t find the exact solution.
Any suggestions will be appreciated. Thanks.
Related
I am attempting to ascertain the membership of a user in an event (the highest level organization structure in my application). Every event holds users with one (and only one) of three access levels (manager, editor or observer). At any point, there has to be at least one manager, that being the creator of the event by default.
I am using the a context processor to query the DB for any events that my currently active user has access to (via holding one of the three roles). This should return a list of all matching event objects.
My problem is that when I add at least two additional users of another role to the event, the list of events for my user shows duplicate entries for that event (one per additional role, and two per additional user in that role beyond the first user). Meaning that if I add two editors, I will see two entries for the event. Adding another two observers, I see four entries total, adding a third observer will show me six entries. Adding only one observer or editor or any number of additional managers (when my own active user is a manager) will not cause duplicates to show.
DB entries and SQL Queries appear correct, and whenever querying for membership filtering for only one role at a time, the results are correct too. Only when combining the Q-object queries via OR does the weird behavior occur.
(For the time being, I have worked around the issue by using .distinct() on the query, but that does not solve the underlying problem.)
Can someone explain to me what I am missing? Code below, including some of my debug prints and the logic behind managing roles for user profiles (even though that seems to be working fine, going by the contents of the DB) for completeness' sake.
I am using Python 3.9, Django 3.2.5 and SQLite 3.31.1
context_processor.py (without .distinct(); User is manager)
from base.models import *
from django.db.models import Q
def eventlist(request):
args = {}
if request.user.id is not None and request.user.id > 0:
print(request.user.profile)
query = (Q(event_observer=request.user.profile) | Q(event_editor=request.user.profile) | Q(event_manager=request.user.profile)) #returns duplicates of an event if more than one editor/observer each exist in it, NOT expected
user_events = Event.objects.filter(query)
args.update({"user_events": user_events})
print(user_events)
print(Event.objects.filter(Q(event_observer=request.user.profile))) #returns nothing, as expected
print(Event.objects.filter(Q(event_editor=request.user.profile))) #returns nothing, as expected
print(Event.objects.filter(Q(event_manager=request.user.profile))) #returns all proper events, as expected
print(Event.objects.filter(Q(event_manager=request.user.profile) | Q(event_editor=request.user.profile))) #returns duplicates of an event if more than one editor exists in it, NOT expected
print(Event.objects.filter(Q(event_observer=request.user.profile) | Q(event_editor=request.user.profile))) #returns nothing, as expected
return args
base/models.py (Events)
import hashlib
from django.db import models
from django.template.defaultfilters import slugify
from base.utils import get_or_none
from django.contrib.auth.models import User
class Event(models.Model):
slug = models.SlugField(max_length=10, default=None)
name = models.CharField(verbose_name="Name", unique=True, max_length=50)
prefix = models.CharField(verbose_name="Prefix (for data entries)", unique=True, max_length=8)
date = models.DateField(verbose_name="Starting Date (DD.MM.YYYY)")
description = models.TextField(verbose_name="Description")
def __str__(self):
return str(self.name)
(... some stuff relating to creating unique slugs and names for new events...)
accounts/models.py (User Profiles)
from django.db import models
from django.contrib.auth.models import User
from base.models import Event
from django.db.models.signals import post_save
from django.dispatch import receiver
class Profile(models.Model):
user = models.OneToOneField(User, related_name="profile", on_delete=models.CASCADE)
is_observer = models.ManyToManyField(Event, related_name="event_observer", blank=True)
is_editor = models.ManyToManyField(Event, related_name="event_editor", blank=True)
is_manager = models.ManyToManyField(Event, related_name="event_manager", blank=True)
def __str__(self):
return self.user.username
def get_permission(self, event):
print('Observer:', self.is_observer.all())
print('Editor:', self.is_editor.all())
print('Manager:', self.is_manager.all())
print()
print(self.is_observer.filter(id=event))
print(self.is_editor.filter(id=event))
print(self.is_manager.filter(id=event))
if self.is_observer.filter(id=event).exists():
return 'observer'
elif self.is_editor.filter(id=event).exists():
return 'editor'
elif self.is_manager.filter(id=event).exists():
return 'manager'
else:
return None
def change_permissions(self, event, level):
if level == "observer":
self.is_observer.add(event)
self.is_editor.remove(event)
self.is_manager.remove(event)
elif level == "editor":
self.is_observer.remove(event)
self.is_editor.add(event)
self.is_manager.remove(event)
elif level == "manager":
self.is_observer.remove(event)
self.is_editor.remove(event)
self.is_manager.add(event)
elif level == "remove":
self.is_observer.remove(event)
self.is_editor.remove(event)
self.is_manager.remove(event)
else:
return 1
return 0
def set_observer(self, event):
return self.change_permissions(event, "observer")
def set_editor(self, event):
return self.change_permissions(event, "editor")
def set_manager(self, event):
return self.change_permissions(event, "manager")
def remove_from_event(self, event):
return self.change_permissions(event, "remove")
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
I'm trying to make a celery task that would send a basic reminder to our users. So in our automated communication project, we have these tasks:
As you can see there are few actions that are different. So for now I have created a logic that fetches all the users from the DB and then continues by checking the time difference. But for now, I only have set-up for 2 hours or more. How should I use it correctly? I do not want to re-write each if statement because it's bad practice. How should I make it clear and reduce the system load?
#app.task
def check_registered_users():
from apps.users.models import User
from apps.notifications.models import AutomatedCommunicationNotifications
day_start = datetime.utcnow().date()
day_end = day_start + timedelta(days=1)
users = User.objects.filter(is_active=True, date_joined__range=(day_start, day_end))
users_that_received_notification = AutomatedCommunicationNotifications.objects.all().values('user__id')
excluded_users = users.exclude(id__in=users_that_received_notification)
for user in excluded_users:
if user.last_login < user.last_login + timedelta(hours=2):
# Sign-up uncompleted Push notification 2 hours after last login
template = SiteConfiguration.get_solo().automated_comms_signup_uncompleted
send_plain_email_task(
email=user.email,
subject=template.subject,
html_message=template.content,
from_email=f'{settings.EMAIL_FROM_PREFIX} <{settings.DEFAULT_FROM_EMAIL}>',
)
P.S AutomatedCommunicationNotifications table is for us to track which user has already received a notification.
class AutomatedCommunicationNotifications(BaseModel):
""" Model for automated comms notifications """
user = models.ForeignKey(User, on_delete=models.CASCADE)
type = models.CharField(
max_length=255,
choices=NotificationTypes.get_choices(),
default=NotificationTypes.EMAIL_NOTIFICATION
)
def __str__(self):
return str(self.user.phone)
You'll have to iterate over your queried users at least once but here are tips that may help:
models.py
class User(...):
# add a field to determine if the user has registered or not
# set this to `True` when a User successfully registers:
is_registered = models.BooleanField(default=False)
class AutomatedCommunicationNotifications(BaseModel):
# add a related name field for easier coding:
user = models.ForeignKey(..., related_name = 'notifications')
tasks.py
# load packages outside of your function so this only runs once on startup:
from django.models import F
from apps.users.models import User
from apps.notifications.models import AutomatedCommunicationNotifications
#app.task
def check_registered_users():
# timestamps:
two_hours_ago = datetime.now() - timedelta(hours=2)
# query for unregistered users who have not received a notification:
users = User.objects.filter(
is_registered = False,
last_login__lt = two_hours_ago # last logged in 2 or more hours ago
).exclude(
notifications__type = "the type"
).prefetch_related(
'notifications' # prejoins tables to improve performance
)
for user in users:
# send email
...
I would do this with a cron job. You can let it run whenever you want, depends on how fast after your give time frame you want to sent this.
You start with making a folder in your app:
/django/yourapp/management/commands
There you make a python file which contains your logic. Make sure to import the right modules from your views.
from django.core.management.base import BaseCommand, CommandError
from yourapp.models import every, module, you, need
from django.utils import timezone
from datetime import datetime, date, timedelta
from django.core.mail import send_mail, EmailMessage
class Command(BaseCommand):
help = 'YOUR HELP TEXT FOR INTERNAL USE'
def handle(self, *args, **options):
# Your logic
I added the crontab to the www-data users crontab like this:
# m h dom mon dow command
45 3 * * * /websites/vaccinatieplanner/venv/bin/python /websites/vaccinatieplanner/manage.py reminder
You can use that crontab file to tweak your optimal time between checks. If you remove the 3 and replace it by a * then you will have it check every 45 mins.
I am trying to set a parameter for a boolean from True to false after a time frame.
For my limited knowledge in Python and Django, I am trying to learn the concept and the logic so that I can apply it in different other places in the project.
here is the Models.py
class Coupon(models.Model):
code = models.CharField(max_length=15, unique=True)
valid_from = models.DateTimeField(blank=True, null=True)
valid_to = models.DateTimeField(blank=True, null=True)
active = models.BooleanField(default=True)
How do set that when the time frame is after valid_to the Active=status becomes False
here is the views.py
class AddCouponView(View):
def post(self, *args, **kwargs):
now = timezone.now()
form = CouponForm(self.request.POST or None)
if form.is_valid():
try:
code = form.cleaned_data.get('code')
order = Order.objects.get(
user=self.request.user, ordered=False)
coupon = Coupon.objects.filter(code__iexact=code, valid_from__lte=now, valid_to__gte=now).exclude(
order__user=self.request.user, max_value__lte=F('used')).first()
if not coupon:
messages.error(self.request, 'You can\'t use same coupon again, or coupon does not exist')
return redirect('core:checkout')
else:
try:
coupon.used += 1
coupon.save()
order.coupon = coupon
order.save()
messages.success(self.request, "Successfully added coupon")
except:
messages.info(self.request, "Max level exceeded for coupon")
return redirect('core:checkout')
except ObjectDoesNotExist:
messages.info(self.request, "You do not have an active order")
return redirect('core:checkout')
One option is to make active dynamic by changing it to a property, e.g.
class Coupon(models.Model):
code = models.CharField(max_length=15, unique=True)
valid_from = models.DateTimeField(blank=True, null=True)
valid_to = models.DateTimeField(blank=True, null=True)
#property
def active(self):
return self.valid_to >= timezone.now()
The downside is you won't be able to use active in filters, see Filter by property.
I'm going to offer a few answers here, from the "easiest" (which also has the most downsides wrt reliability, timeliness of execution, and such), to a much heavier solution that probably goes beyond your needs; only you know where to strike that balance for a given project.
One solution would to override the save() method to add logic checking whether the coupon has expired; this is probably insufficient as coupons are likely rarely saved after creation, and could easily sit in the wrong state for an unacceptable amount of time.
A more flexible solution (without adding dependencies) would be to write a management command to check coupon expiration and invalidate any found expired, and add it to the system cron. That still has some drawbacks; if the cron runs every hour, coupons could be live for an hour longer than intended. Also cron lives completely apart from your application and if you're not already using it for other tasks, it may be hard to incorporate checking a separate set of logs, etc. into your maintenance/workflow.
The next step up in reliability may be your Goldilocks solution: Django-Cron lets you schedule jobs similarly to cron but handles execution, result tracking, logging, etc. within your existing Django app.
Most heavy, and overkill for many projects, but reliable and flexible enough for just about anything, is using django-celery-beat. This requires Celery and so it's much more work to setup and maintain than is necessary for personal projects. But it's scalable, very flexible, etc.
So I have implemented a subscription product to my website.
When they start the subscription, the current date and time is stored into the database. When they cancel the subscription, a period of time is added onto the start date so I know when to cancel the subscription and change some values elsewhere.
This is fine, I know how to use Django and Python, however, I am stuck on the last bit of, when the cancel date comes around in the future.
Question: How do I execute a function when the cancel date (in the db) is equal to current date and time.
Below is an simple example of the model I will be using:
models.py
class Subscriptions(models.Model):
subscription_id = models.AutoField(primary_key=True)
start_date = model.DateTimeField(auto_now_add=True)
cancel_date = model.DateTimeField(auto_now_add=False)
person_id = model.ForeignKey('Persons')
class Persons(models.Model):
person_id = models.AutoField(primary_key=True)
value_to_change = models.BooleanField()
Before you ask I have not attempted any code as I couldn't find a solution for this problem. Thanks <3
Without Celery, installed on UNIX system providing CRON (cron doku: e.g. https://www.computerhope.com/unix/ucrontab.htm):
write a Command https://docs.djangoproject.com/en/2.1/howto/custom-management-commands/ that fetches the objects for which cancel_date is in the past and that have not been cancelled yet. (If you do the lookup with datetime.now() (a very precise lookup with the finest granularity), you would have to be more than lucky to find anything.)
You should add another date field that tells you when the system actually ran the cancellation, and you should allow both the cancel_date and cancelled_date to be null.
# models.py
class Subscriptions(models.Model):
subscription_id = models.AutoField(primary_key=True)
start_date = model.DateTimeField(auto_now_add=True)
cancel_date = model.DateTimeField(auto_now_add=False, null=True)
cancelled_date = model.DateTimeField(null=True)
person_id = model.ForeignKey('Persons')
# under myapp/management/command/cancellation.py
class CancellationCommand(BaseCommand):
def handle(self, *args, **options):
now = datetime.now()
to_cancel_qs = Subscriptions.objects.exclude(
cancelled_date__isnull=False).filter(
cancel_date__lte=now)
for sub in to_cancel_qs.all():
# do your cancelling
sub.cancelled_date = now
sub.save()
# or: to_cancel_qs.update(cancelled_date=now)
install a cron job that runs this command via ./manage.py your_command at a regular time
AIM
For the purposes of an alarm clock, I am attempting to 'get' the specific Alarm created by the User in the SetAlarmForm.
From the other answers on the same topic (Q1, Q2, Q3), I am attempting the line: objx = Alarm.objects.get(id=run_alarm.request.id). Perhaps, I am missing something obvious or the version of Celery has been updated?
ERROR
[ERROR/ForkPoolWorker-2] raised unexpected: DoesNotExist('Alarm matching query does not exist')
CODE
Models.py
class Alarm(models.Model):
""" Model representing each Alarm """
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
Views.py
class AlarmCreateView(LoginRequiredMixin, CreateView):
""" CreateView for User to create the Alarm object """
model = Alarm
form_class = SetAlarmForm
template_name = 'weather_alarm/set_alarm.html'
login_url = "/login/"
def form_valid(self, form):
self.create_alarm_object(self.request, form)
run_alarm.delay()
return HttpResponseRedirect(self.get_success_url())
Tasks.py
import time
from celery import Celery, shared_task, current_task
from datetime import datetime
from .models import Alarm
#shared_task
def run_alarm():
""" Function to organise the steps for the alarm using Celery """
objx = Alarm.objects.get(id=run_alarm.request.id)
second_countdown = objx.get_alarm_length() # get the length of the alarm, in seconds
time.sleep(second_countdown) # wait for the alarm time
conditions_satisfied = objx.check_conditionals() # check if conditionals are satisfied
if conditions_satisfied == True: # conditions are satified
print("Ring ring!")
return True
else: # conditions aren't satisfied
print("I'm only sleeping!")
return True
The simplest way to fix this would be to make alarmID an argument passed to your task:
Tasks.py
#shared_task
def run_alarm(alarmID):
objx = Alarm.objects.get(id = alarmID)
You'll need to pass this ID when calling your task in your view:
Views.py
...
def form_valid(self, form):
#making an assumption about what create_alarm_object returns here; you get the idea
newAlarm = self.create_alarm_object(self.request, form)
run_alarm.delay(newAlarm.id)
Notice how you can pass the argument to run_alarm by giving it to delay here. Further reading: http://docs.celeryproject.org/en/latest/userguide/calling.html#example
The reason you are encountering your error is that request.id is going to point at the task ID of the individual asynchronous celery task being run, not at the ID of the alarm object. Further reading: http://docs.celeryproject.org/en/latest/userguide/tasks.html?highlight=request#task-request