Cronjob to periodically refresh cache for django view - python

I've had some trouble trying to reset my cache every hour for a particular django view.
Right now, I am using the cache_page decorator to cache my view using Memcached. But the cache expires after a while and the request is uncached from some users.
#cache_page(3600)
def my_view(request):
...
How do I write my own django manage.py command to refresh my cache for this view every hour from cron?
In other words, what do I put in my refresh_cache.py file mentioned in the answer here:
Django caching - can it be done pre-emptively?
Thanks!

In your app, you can create a folder called management which contains another folder commands and an empty __init__.py file. Inside commands you create another __init__.py and a file where you write your custom command. Let's called it refresh.py:
# refresh.py
from django.core.management.base import BaseCommand, CommandError
from main.models import * # You may want to import your models in order to use
# them in your cron job.
class Command(BaseCommand):
help = 'Posts popular threads'
def handle(self, *args, **options):
# Code to refresh cache
Now you can add this file to your cron jobs. You can take a look at this tutorial but basically you use crontab -e to edit your cron jobs and crontab -l to see which cron jobs are running.
You can find all of this and more in the Django documentation.

I want to expand on Roberts answer to fill out the # Code to refresh cache
Timezome+location make caches much hard to work with, in my case I just disabled them, also I am not sure about using the test methods in the application code, but it seems to work nicely.
from django.core.management.base import BaseCommand, CommandError
from django.test.client import RequestFactory
from django.conf import settings
from ladder.models import Season
from ladder.views import season as season_view
class Command(BaseCommand):
help = 'Refreshes cached pages'
def handle(self, *args, **options):
"""
Script that calls all season pages to refresh the cache on them if it has expired.
Any time/date settings create postfixes on the caching key, for now the solution is to disable them.
"""
if settings.USE_I18N:
raise CommandError('USE_I18N in settings must be disabled.')
if settings.USE_L10N:
raise CommandError('USE_L10N in settings must be disabled.')
if settings.USE_TZ:
raise CommandError('USE_TZ in settings must be disabled.')
self.factory = RequestFactory()
seasons = Season.objects.all()
for season in seasons:
path = '/' + season.end_date.year.__str__() + '/round/' + season.season_round.__str__() + '/'
# use the request factory to generate the correct url for the cache hash
request = self.factory.get(path)
season_view(request, season.end_date.year, season.season_round)
self.stdout.write('Successfully refreshed: %s' % path)

Related

Scheduled tasks in Django to update data in the models (database)

I need to schedule tasks via Pythonanywhere, to update some records in my database.
As an example, I do have Events as models:
models.py
class Events(models.Model):
event_title = models.CharField(max_length=300)
event_info = models.TextField(max_length=2000)
event_date = models.DateField(default=now)
event_time = models.TimeField(default='00:00')
event_location = models.CharField(max_length=150)
event_status = models.CharField(max_length=30, choices=EVENT_STATUS_CHOICES, default='New Event')
Also within that Events class in models.py I have the following:
def save(self, *args, **kwargs):
if date.today() > self.event_date:
self.event_status = 'Completed Event'
super(Events, self).save(*args, **kwargs)
Thanks to that, all events in the past, change their status to Completed. Problem is, you need to refresh the page to have the save function run on the production website.
It also drives some other functions, e.g. showing the user the count of active events - this one as well, in order to show the right active number of events, has to be refreshed manually.
By scheduling tasks in PA to run overnight, iterating through all Events, and if in the past, changing their status.
I created the below file and uploaded it to PA Tasks.
tasks.py
from .models import Events
from datetime import date
def verify_events():
all_events = Events.objects.all()
for event in all_events:
if event.event_date < date.today():
event.event_status = 'Completed Event'
event.save()
I got an error from PA when the task run:
Traceback (most recent call last):
File "/home/xxxxx/XXXXXXX/xxxxxx/tasks.py", line 1, in <module>
from .models import Events
ImportError: attempted relative import with no known parent package
2022-10-24 23:30:30 -- Completed task, took 14.35 seconds, return code was 1.
Looks like tasks.py could not read it from the database (->import from Models)
What's the best way to set up PA tasks to run and write to the database?
EDIT
This is indeed an issue with the working directory while on PythonAnywhere.
https://help.pythonanywhere.com/pages/ScheduledTasks/
I did add the below to the tasks.py but still the same error (ModuleNotFoundError: No module named XXX):
import os
path = "/home/myusername/myprojectname/mainfolder/"
os.chdir(path)
Folder structure - both tasks.py and models.py are in the same folder:
myprojectname/mainfolder/tasks.py
myprojectname/mainfolder/models.py
__init__.py is there.
Still not working.
So, while on PA, should I change some settings of the working directory? Or how to fix it, so I can import models (i.e. Events from db) and update using Tasks?
ImportError has nothing to do with the database, it's a Python error raised on your line 1. Its description is pretty accurate: "attempted relative import with no known parent package"

I want to send terminal print data to database

I'm new to Django, I have created a selenium project in which It automates mobile recharge. After completion of "recharge successful" I need it to send a successful receipt into my database. I used print(order_id.text) to get receipt in my terminal. now I don't know how to send that receipt to my database.
Probably the easiest way to implement this is with a custom management command [Django-doc]. In your amazon app, yoou can define a management command:
amazon/
management/
commands/
amazonpay.py
# …
In that amazonpay.py file, you then implement the custom management command:
# amazon/management/commands.amazonpay.py
from django.core.management.base import BaseCommand
from amazon.models import Amazon
class Command(BaseCommand):
help = 'Some description...'
def handle(self, *args, **options):
# … run selenium …
Amazon.objects.create(
o=ord_id
)
You can then run this command with:
python3 manage.py amazonpay
In fact runserver, makemigrations, etc. are all defined as management commands as well.

Using Python 3.7 contextvars to pass state between Django views

I'm building a single database/shared schema multi-tenant application using Django 2.2 and Python 3.7.
I'm attempting to use the new contextvars api to share the tenant state (an Organization) between views.
I'm setting the state in a custom middleware like this:
# tenant_middleware.py
from organization.models import Organization
import contextvars
import tenant.models as tenant_model
tenant = contextvars.ContextVar('tenant', default=None)
class TenantMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
user = request.user
if user.is_authenticated:
organization = Organization.objects.get(organizationuser__is_current_organization=True, organizationuser__user=user)
tenant_object = tenant_model.Tenant.objects.get(organization=organization)
tenant.set(tenant_object)
return response
I'm using this state by having my app's models inherit from a TenantAwareModel like this:
# tenant_models.py
from django.contrib.auth import get_user_model
from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver
from organization.models import Organization
from tenant_middleware import tenant
User = get_user_model()
class TenantManager(models.Manager):
def get_queryset(self, *args, **kwargs):
tenant_object = tenant.get()
if tenant_object:
return super(TenantManager, self).get_queryset(*args, **kwargs).filter(tenant=tenant_object)
else:
return None
#receiver(pre_save)
def pre_save_callback(sender, instance, **kwargs):
tenant_object = tenant.get()
instance.tenant = tenant_object
class Tenant(models.Model):
organization = models.ForeignKey(Organization, null=False, on_delete=models.CASCADE)
def __str__(self):
return self.organization.name
class TenantAwareModel(models.Model):
tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE, related_name='%(app_label)s_%(class)s_related', related_query_name='%(app_label)s_%(class)ss')
objects = models.Manager()
tenant_objects = TenantManager()
class Meta:
abstract = True
In my application the business logic can then retrieve querysets using .tenant_objects... on a model class rather than .objects...
The problem I'm having is that it doesn't always work - specifically in these cases:
In my login view after login() is called, the middleware runs and I can see the tenant is set correctly. When I redirect from my login view to my home view, however, the state is (initially) empty again and seems to get set properly after the home view executes. If I reload the home view, everything works fine.
If I logout and then login again as a different user, the state from the previous user is retained, again until a do a reload of the page. This seems related to the previous issue, as it almost seems like the state is lagging (for lack of a better word).
I use Celery to spin off shared_tasks for processing. I have to manually pass the tenant to these, as they don't pick up the context.
Questions:
Am I doing this correctly?
Do I need to manually reload the state somehow in each module?
Frustrated, as I can find almost no examples of doing this and very little discussion of contextvars. I'm trying to avoid passing the tenant around manually everywhere or using thread.locals.
Thanks.
You're only setting the context after the response has been generated. That means it will always lag. You probably want to set it before, then check after if the user has changed.
Note though that I'm not really sure this will ever work exactly how you want. Context vars are by definition local; but in an environment like Django you can never guarantee that consecutive requests from the same user will be served by the same server process, and similarly one process can serve requests from multiple users. Plus, as you've noted, Celery is a yet another separate process again, which won't share the context.

Django - create a class instance in AppConfig.ready() only once

I need to create a class instance (lets say backend requests session) on the app startup(runserver), and I don't want to rewrite this session after running other management command. How can I achieve this? I tried several approaches and I'm not sure why something like this doesn't work.
# app/apps.py
class MyConfig(AppConfig):
....
requests_session = None
....
def ready(self):
if MyConfig.requests_session is None:
MyConfig.requests_session = requests.Session()
Unfortunately, the condition is always met and the session is recreated. This approach is recommended in the documentation though.
Other solution for me would be to run MyConfig.ready() only after using selected subset of management commands, is that possible?
Is there completely different better way for me to store requests session?
TIA
I think it should work if you use an instance variable instead of a class variable:
# app/apps.py
class MyConfig(AppConfig):
def __init__(self, app_name, app_module):
super(MyConfig, self).__init__(app_name, app_module)
self.requests_session = None
def ready(self):
if self.requests_session is None:
self.requests_session = requests.Session()
The question now is how to access this instance variable elsewhere. You can do that like so:
from django.apps import apps
# Here myapp is the label of your app - change it as required
# This returns the instance of your app config that was initialised
# at startup.
my_app_config = apps.get_app_config('myapp')
# Use the stored request session
req = my_app_config.requests_session
Note that this instance variable only exists in the context of the current process. If you run a management command in a separate process (e.g., manage.py ...) then that will create a new instance of each app.

Execute Django shell command as cron

I'm try to Execute Django shell command as cron,
I have some queries and objects tasks to search and read and write using the models and queries of my django app.
How can I execute this 1 or 2 times a day?
For example, how can I run these queries periodically:
from django.contrib.auth.models import User
from perfil.models import *
for user in usuarios:
profiles = Perfil.objects.filter(user=user)
create_task = Task.objects.create(user=user)
Take a look at Custom management commands for django.
As a basic example:
from django.core.management.base import BaseCommand, CommandError
from django.contrib.auth.models import User
from perfil.models import *
class Command(BaseCommand):
help = 'What does this do? '
def handle(self, *args, **options):
for user in usuarios:
profiles = Perfil.objects.filter(user=user)
create_task = Task.objects.create(user=user)
On a side note, you should be more explicit about your import and not use from perfil.models import *.
From that, you can execute the command based on the file you saved it in. If you saved the file in yourapp/management/commands/dofunstuff.py than you could execute it via python manage.py dofunstuff.

Categories