Django standalone run in cron - python

I want to run automatic newsletter function in my crontab, but no matter what I try - I cannot make it work. What is the proper method for doing this ?
This is my crontab entry :
0 */2 * * * PYTHONPATH=/home/muntu/rails python2.6 /home/muntu/rails/project/newsletter.py
And the newsletter.py file, which is located in the top folder of my django project :
#! /usr/bin/env python
import sys
import os
os.environ["DJANGO_SETTINGS_MODULE"] = "project.settings"
from django.core.management import setup_environ
from project import settings
setup_environ(settings)
from django.template.loader import get_template, render_to_string
from django.template import Context
from django.core.mail import EmailMultiAlternatives
from django.utils.translation import ugettext_lazy as _
from django.core.mail import send_mail
from django.conf import settings
from project.utilsFD.models import *
from django.http import HttpResponse, HttpResponseRedirect, Http404
def main(argv=None):
if argv is None:
argv = sys.argv
template_html = 'static/newsletter.html'
template_text = 'static/newsletter.txt'
newsletters = Newsletter.objects.filter(sent=False)
adr = NewsletterEmails.objects.all()
for a in adr:
for n in newsletters:
to = a.email
from_email = settings.DEFAULT_FROM_EMAIL
subject = _(u"Newsletter - Method #1")
text_content = render_to_string(template_text, {"title": n.title,"text": n.text, 'date': n.data, 'email': to})
html_content = render_to_string(template_html, {"title": n.title,"text": n.text, 'date': n.data, 'email': to})
msg = EmailMultiAlternatives(subject, text_content, from_email, [to])
msg.attach_alternative(html_content, "text/html")
msg.content_subtype = "html"
msg.send()
n.sent = True
n.save()
if __name__ == '__main__':
main()
What am I doing wrong ? The function itself was working without any problems when run as django app, but when I was trying to run it from console, it gave me :
Traceback (most recent call last):
File "newsletter.py", line 7, in <module>
from project import settings
ImportError: No module named project
And it does not work from cron at all.

Try changing your cron entry to:
0 */2 * * * cd /home/muntu/rails && python2.6 /home/muntu/rails/project/newsletter.py
This will ensure that the "rails" directory is in python's path. If you want to set the PYTHONPATH, then create a shell script:
#!/bin/sh
export PYTHONPATH=/home/muntu/rails
python2.6 /home/muntu/rails/project/newsletter.py
and put the shell script in the cron entry.

Related

How to run a python script in Django?

I am new to Django, and I'm trying to import one of my models in a script as we do it in views.py. I'm getting an error:
Traceback (most recent call last):
File "CallCenter\make_call.py", line 3, in <module>
from .models import Campaign
ModuleNotFoundError: No module named '__main__.models'; '__main__' is not a package
My file structure is like:
MyApp\CallCenter\
CallCenter contains __init__.py, make_call.py, models.py, views.py and MyApp has manage.py
from twilio.rest import Client
from twilio.twiml.voice_response import VoiceResponse, Say, Dial, Number, VoiceResponse
from .models import Campaign
def create_xml():
# Creates XML
response = VoiceResponse()
campaign = Campaign.objects.get(pk=1)
response.say(campaign.campaign_text)
return response
xml = create_xml()
print(xml)
In general, it's better to refactor "ad-hoc" scripts – anything you might run manually from a command line, say – into management commands.
That way the Django runtime is set up correctly once things get to your code, and you get command-line parsing for free too.
Your make_call.py might become something like this:
CallCenter/management/commands/make_call.py
from twilio.rest import Client
from twilio.twiml.voice_response import VoiceResponse, Say, Dial, Number, VoiceResponse
from CallCenter.models import Campaign
from django.core.management import BaseCommand
def create_xml(campaign):
# Creates XML
response = VoiceResponse()
response.say(campaign.campaign_text)
return response
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument("--campaign-id", required=True, type=int)
def handle(self, campaign_id, **options):
campaign = Campaign.objects.get(pk=campaign_id)
xml = create_xml(campaign)
print(xml)
and it would be invoked with
$ python manage.py make_call --campaign-id=1
from wherever your manage.py is.
(Remember to have an __init__.py file in both the management/ and the management/commands/ folders.)

Does __init__.py have to be in every directory of python application?

__init__.py are use to mark directories on disk as a Python package directories. lets say we have the files
py/src/__init__.py
py/src/user.py
and py is on your path, you can import the code in user.py as:
import src.module
or
from src import user
If we remove the __init__.py file, Python will no longer look for submodules inside that directory, so attempts to import the module will fail.
But on my app the __init__.py is not working.
__init__ .py
__all__ = ['models', 'common']
from src.models.user import User
from src.models.post import Post
from src.models.blog import Blog
When I run python app.py
errors:
Traceback (most recent call last):
File "src/app.py", line 5, in <module>
from src.models.user import User
ModuleNotFoundError: No module named 'src'
App.py file:
import os
import sys
from flask import Flask, render_template, request, session
from src.models.user import User
app = Flask(__name__) #'__main__'
#app.route('/') # 'OUrsite.com/api/'
# def hello world ot test the first api call
def hello_method():
return render_template('login.html')
#app.route('/login')
def login_user():
email = request.form['email']
password = request.form['password']
if User.login_valid(email, password):
User.login(email)
return render_template("profile.html", email=session['email'])
if __name__ == '__main__':
app.run(port = 9100)
Am I missing something?
There are two things to understand.
There is a difference in root directory of virtualenv and your system environment.
Virtualenv sets your project dir to it's root. But in case you are not using virtualenv, the root would be your system directory (at least, in my experience).
So, while creating __init__py, keep in mind that, if there is no virtualenv or you haven't imported anything, it can be empty.
simple __init_.py examples:
# in your __init__.py
from file import File
# now import File from package
from package import File
My __init__.py:
import src
from src import *
from .models.user import User
from .models.post import Post
from .models.blog import Blog
And when you import in app.py or src/models/user.py which is in a same directory & subdirectory, you shall not include src:
from models.user import User
from common.database import Database
You didn't import src first.
import src
__all__ = ['models', 'common']
from src.models.user import User
from src.models.post import Post
from src.models.blog import Blog
If this is src/__init__.py, you can use a relative import:
from __future__ import absolute_import
from .models.user import User
from .models.post import Post
from .models.blog import Blog

Crontab python django issue

The following works as expected:
python /usr/share/str8RED/manage.py getLiveResults
However, nothing happens when I use the following cronjob:
*/1 * * * * python /usr/share/str8RED/manage.py getLiveResults
Using the link below I have managed to create a error log:
http://matthewwittering.com/blog/django-tips/running-a-django-management-commands-with-crontab.html
This informs me that:
Traceback (most recent call last):
File "/usr/share/str8RED/manage.py", line 8, in <module>
from django.core.management import execute_from_command_line
ImportError: No module named django.core.management
I can get cronjab working and running every minute with echo "Hello World". Any help would be appreciated, many thanks, Alan.
Contents of getLiveResults.py:
from django.core.management import BaseCommand
from straightred.models import StraightredTeam
from straightred.xmlsoccer import XmlSoccer
#The class must be named Command, and subclass BaseCommand
class Command(BaseCommand):
# Show this when the user types help
help = "My test command"
# A command must define handle()
def handle(self, *args, **options):
xmlsoccer = XmlSoccer(api_key='XYZ', use_demo=False)
teams = xmlsoccer.call_api(method='GetAllTeamsByLeagueAndSeason',
seasonDateString='1617',
league='English League Championship')
numberOfTeamsUpdated = 0
for team in teams:
if '{http://xmlsoccer.com/Team}Team_Id' in team.keys():
teamUpdate = StraightredTeam(teamid=team['{http://xmlsoccer.com/Team}Team_Id'],
teamname=team['{http://xmlsoccer.com/Team}Name'],
country=team['{http://xmlsoccer.com/Team}Country'],
stadium=team['{http://xmlsoccer.com/Team}Stadium'],
homepageurl=team['{http://xmlsoccer.com/Team}HomePageURL'],
wikilink=team['{http://xmlsoccer.com/Team}WIKILink'],
currentteam=1)
teamUpdate.save()
numberOfTeamsUpdated = numberOfTeamsUpdated + 1
self.stdout.write("Hello world!")
If you are using virtual env, then you need to activate the environment,
maybe something like:
*/1 * * * * /usr/share/str8RED/.env/bin/python /usr/share/str8RED/manage.py getLiveResults

How to run python file in cron job

I need to run this file:
from apps.base.models import Event
from apps.base.models import ProfileActiveUntil
from django.template import Context
from django.db.models import Q
import datetime
from django.core.mail import EmailMultiAlternatives
from bonzer.settings import SITE_HOST
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from bonzer.settings import send_mail, BONZER_MAIL, BONZER_MAIL_SMTP, BONZER_MAIL_USER, BONZER_MAIL_PASS, BONZER_MAIL_USETLS
today = datetime.date.today()
monthAgo = today + datetime.timedelta(days=1)
monthAgoMinusOneDay = today + datetime.timedelta(days=2)
events = Event.objects.all()
ProfileActiveUntils = ProfileActiveUntil.objects.filter(Q(active_until__range=(monthAgo, monthAgoMinusOneDay)))
msg = MIMEMultipart('alternative')
msg['Subject'] = "Novim dogodivscinam naproti"
msg['From'] = BONZER_MAIL
msg['To'] = 'jjag3r#gmail.com'
text = u'bla'
html = u'bla'
send_mail(msg_to=msg['To'], msg_subject=msg['Subject'], msg_html=html, msg_text=text)
I execute it like this: */2 * * * * /usr/local/bin/python2.7 /home/nezap/webapps/bonzer/bonzer/apps/base/alert.py
But I get error: No module named apps.base.models.
Important fact is that I can't install virtualenv on server because I don't have permissions. Also I'm kind of newbie on this stuff so I don't have a lot of skills on servers or python.
Thank you.
cron does not read rc shell files so you need to define the enviroment variable PYTHONPATH to include the location of the apps package and all other module files that are required by the script.
PYTHONPATH=/usr/local/lib/python2.7:/usr/lib/python2.7
*/2 * * * * /usr/local/bin/python2.7 /home/nezap/webapps/bonzer/bonzer/apps/base/alert.pyr
I would assume this is a problem with your cwd (current working directory). An easy way to test this would be to go to the root (cd /) then run:
python2.7 /home/nezap/webapps/bonzer/bonzer/apps/base/alert.py
You should get the same error. The path you will want to use will depend on the place where you normally run the script from. I would guess it would either be:
/home/nezap/webapps/bonzer/bonzer/apps/base
or
/home/nezap/webapps/bonzer/bonzer/
So your solution would either be:
*/2 * * * * cd /home/nezap/webapps/bonzer/bonzer/apps/base && /usr/local/bin/python2.7 ./alert.py
or
*/2 * * * * cd /home/nezap/webapps/bonzer/bonzer && /usr/local/bin/python2.7 ./apps/base/alert.py
basically you are telling cron to change directory to that path, then if that works(the &&) run the following command.

Using environment dependent django settings inside fabric task

I'm trying to use the database configuration set on settings files to make a database dump using fabric.
There's more than one settings file, so I'd like to be able to do so based on the environment I choose.
by now, my task is like this
def dump_database():
with cd('~/project_folder'), prefix(WORKON_VIRTUALENV):
django.settings_module(env.settings)
from django.conf import settings
dbname = settings.DATABASES['default']['NAME']
dbuser = settings.DATABASES['default']['USER']
dbpassword = settings.DATABASES['default']['PASSWORD']
fname = '/tmp/{0}-backup-{1}.sql.gz'.format(
dbname,
time.strftime('%Y%m%d%H%M%S')
)
run('mysqldump -u %s -p=%s %s | gzip -9 /tmp/backup-%s.sql.gz' % (
dbuser,
dbpassword,
dbname,
fname))
But I'm getting an ImportError:
ImportError: Could not import settings 'project.settings.production'
I've tried to use shell_env() to set the DJANGO_SETTINGS_MODULE instead of django.settings_module(env.settings), with the same result.
I use a task to change the environment based on a environment dict:
def environment(name):
env.update(environments[name])
env.environment = name
This way, I want to be able to create a dump from multiple hosts like:
fab environment:live dump_database
fab environment:otherhost dump_database
Without having to reproduce database settings from all hosts on fabfile.
Importing your Django settings file in fabric is explained here.
http://fabric.readthedocs.org/en/1.3.3/api/contrib/django.html
Quoting from the above link:
from fabric.api import run
from fabric.contrib import django
django.settings_module('myproject.settings')
from django.conf import settings
def dump_production_database():
run('mysqldump -u %s -p=%s %s > /tmp/prod-db.sql' % (
settings.DATABASE_USER,
settings.DATABASE_PASSWORD,
settings.DATABASE_NAME
))
NOTE: I don't answer the question but offer a different solution
I had the same problem.. so I did custom .py script like that:
I created a file named dump_db.py (placed next to fabfile.py for example, that is on the remote machine)
import os
import sys
from datetime import datetime
from django.conf import settings
def dump_mysql():
os.environ.setdefault("DJANGO_SETTINGS_MODULE", SETTINGS_MODULE)
DB_NAME = settings.DATABASES['default']['NAME']
DB_USER = settings.DATABASES['default']['USER']
DB_PASSWORD = settings.DATABASES['default']['PASSWORD']
dump_file_name = '{time}_{db_name}.sql'.format(
time=datetime.now().strftime('%Y_%m_%d'),
db_name=DB_NAME,
)
os.system('mysqldump -u {db_user} -p{db_password} {db_name} > {to_file}'.format(
db_user=DB_USER,
db_password=DB_PASSWORD,
db_name=DB_NAME,
to_file=dump_file_name,
))
return dump_file_name
if __name__ == '__main__':
try:
SETTINGS_MODULE = sys.argv[1:].pop()
except IndexError:
SETTINGS_MODULE = 'project_name.settings'
print dump_mysql()
As you see sys.argv[1:].pop() tries to take optional argument (the setting module in this case).
So in my fabfile:
import os
from fabric.api import env, local, run, prefix, cd
.....
def dump():
current_dir = os.getcwd()
with prefix('source {}bin/activate'.format(env.venv)), cd('{}'.format(env.home)):
dumped_file = run('python dump_db.py {}'.format(env.environment)) # the optional argument given
file_path = os.path.join(env.home, dumped_file)
copy_to = os.path.join(current_dir, dumped_file)
scp(file_path, copy_to)
def scp(file_path, copy_to):
local('scp {}:{} {}'.format(env.host, file_path, copy_to))
where env.environment = 'project_name.settings.env_module'
And this is how I dump my DB and copy it back to me.
Hope it comes handy to someone! :)

Categories

Resources