Running a custom python script directly from the Django Admin webpage - python

I have a python script written that takes an input from one model, queries it, and appends the various results from the query to another model through a ForeignKey relationship. It works great from the python shell, but I was wondering if there is a way to run it from the admin webpage so that every time a new object for the first model is submitted, it runs the script and updates the database automatically for the other model. I'm using the Django admin interface as part of development for staff to do data entry since I've found it's a very flexible interface. The script is written specifically for this app, so it is on the app's folder.

I was surprised that this wasn't already answered.
Either wrap the existing script as a management command, or import it into a management command.
Once you've done that, you can override the Admin view in question, like this..
from django.contrib.admin import AdminSite
from django.views.decorators.cache import never_cache
class MyAdminSite(AdminSite):
#never_cache
def index(self, request, extra_context=None):
# do stuff
Then, you create an instance of this class, and use this instance, rather than admin.site to register your models.
admin_site = MyAdminSite()
Then, later:
from somewhere import admin_site
class MyModelAdmin(ModelAdmin):
...
admin_site.register(MyModel, MyModelAdmin)
Lastly, in that overriding view, you can use management.call_command from your code to call the management command. This lets you use it both from the commandline, and from inside your code - and if you need to, you can schedule it from cron, too. :)

Related

Django: Where to store a main object and reference it from views.py + run repeating code?

I am building an app with Django to automate a reservation which I need to make every day. Django is used to provide a web interface to monitor and control this.
However, I cannot figure out where to put my code within the Django files. Before trying Django I just had a while True loop that made the reservation as soon as a condition was true. To do this in Django, I need to create a main object on startup and reference this from different views in the views.py file. And I need to run some kind of schedule that checks the condition if the reservation is to be made. All of this in parallel to the actual website code.
Using celery seems complicated and overkill for this. Is there a main Django object that is created on startup and can be accessed from other files (such as views.py)? Then I could create a parent class of this class that holds my main code and allows for starting and managing parallel jobs.
E.g., I looked at AppConfig, but don't know how to reference this object from other files...
class ReservationAppConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'reservation_app'
driver = None
def ready(self):
# python manage.py runserver runs the ready method twice — once in each of two processes —
# but we only want to run it once.
if os.environ.get('RUN_MAIN', None) != 'true':
self.driver = Safari()
self.driver.get(LIB_ADRESS)
I suggest the next approach:
You create management command with your while True: script.
from django.core.management import BaseCommand
class Command(BaseCommand):
def handle(self, *args, **options):
while True:
... # your previous script + storing data into DB via Django models
This script changes state in database. So after each cycle it saves or updates some [Django models])https://docs.djangoproject.com/en/4.0/topics/db/models/).
Django View get these models from DB and displays data on web UI.
It seems more stable, than directly accessing script's object from web threads (you risk to trap into threading-related errors).

Detect change in apps and user who made changes by a separate app Django.

I'm trying to create an app, say activitylogapp in the project that detects if in another app say Employeeapp some models are changed/updated and keep a log. I don't want touch Employeeapp. I can access changes by using signals in the models.py of activitylodapp. By this:
from django.db.models.signals import post_save
from anotherapp.models import Employee
from django.dispatch import receiver
#receiver(post_save, sender=Employee)
def save_handler(sender, instance, created, **kswargs):
"Things I want to do"
The problem is I also want to access which user made these changes, like request.user.username that is used in views.py.
Is it possible without explicitly injecting request object from view to activitylog app?
You can add a primitive middleware into your "activitylogapp" with process_request to store the request.
As for storing the request, I see 2 options:
Ugly but fast to implement. Save the request globally. It shouldn't affect anything as you get a fresh new cloned thread for each request, which dies right after the request has been processed.
More sophisticated, without globals. Utilize the fact, that Django signals create weak references to their receiver functions. So you can attach save_handler to the request itself, and they will get GC-ed together in the end. Something like that:
class MyMiddleware(object):
def process_request(request):
def save_handler(sender, instance, created, **kswargs):
user = request.user
"do the stuff you want to do"
# without the following line, the save_handler will be
# garbage collected right away since nothing references it
request._my_save_handler_instance = save_handler
post_save.register(save_handler, ...)

Django ModelAdmin not registered

First, I want to inform that I did check other related questions but their solutions were very simple (improper registration, settings, etc.). This problem is weird in a way that I haven't faced in 3+ years of developing with Django. So here comes:
I have an app that's 90% celery tasks, so it only has two models. They are simple. I have two ModelAdmin classes defined in the admin.py of the app, one for each model. These are simple as well. They are both registered properly. The app is in the INSTALLED_APPS.
All kosher and without any customization of templates, tags or forms, just a plain admin.py:
from myapp.models import (Something, OtherModel)
class SomethingAdmin(admin.ModelAdmin):
# admin config ...
class OtherModelAdmin(admin.ModelAdmin):
pass # Trying anything at this point...
admin.site.register(Something, SomethingAdmin)
admin.site.register(OtherModel, OtherModelAdmin)
So simple it cannot fail, but it does: one of them doesn't show up in the admin. It's simply not registered (404 on manual url access). The other one does show up, and does work properly.
Validation on that invisible admin works because when I add a strange value to its list_display, Django does raise the proper exception (ImproperlyConfigured). So it does reads it, it just fails to register it. If I comment the visible one out, the app is simply removed from the admin (of course, it thinks no modeladmins).
So, in short, one of the ModelAdmins is invisible, while the other one in the same file and with nearly identical configuration, isn't. Any thoughts?
EDIT: Answers to a few suggestions I expect: Yes, the model is working properly (and heavily unit tested), and I have created/saved instances and they are in the db. Yes, I did restart the server. Yes, the computer is plugged in and it's currently on. :)
As it turns out, the problem was in the structure: models was a package, instead of the more usual module. It seems that even if you make the model available at package level (import it in init.py), Django still doesn't know in what app it should be included.
What you need to do is specify the app_label in its Meta class. So the model now becomes:
from django.db import models
class OtherModel(models.Model):
class Meta:
app_label = 'someapp'
# other meta attrs
# Model attrs ...
Odd that it needs to be specified, when the model is available in the usual models.SomeModel namespace, but at least the solution's simple enough.
BTW, as you can guess the other model did include this Meta attr, I just didn't notice it before. LOL.

Do I need to create a separate class in my models.py when using the django.contrib.auth.models import user?

The import statement import the needed parts. but is the "user" class already made when you put that into your installed apps? or do you still need to clarify in models.py in order to make the table in the db? or can someone expand on how to use django users and sessions? I'm looking over the django docs right now and they all just go over how to use the thing once. they never put the code in a syntax where users are going to be the ones using the code through a browser and not you through a python shell.
All installed apps can contribute to the database schema. django.contrib.auth.models contributes, among others, the auth_user table behind the django.contrib.auth.models.User model, therefore you do not have to worry about recreating it unless you have a specific reason to do so.
There's a number of things going on here. As you're aware, Django comes with a number of "contrib" packages that can be used in your app. You "activate" these by putting them into your INSTALLED_APPS.
When you run python manage.py syncdb, Django parse the models.py files of every app in INSTALLED_APPS and creates the associated tables in your database. So, once you have added django.contrib.auth to your INSTALLED_APPS and ran syncdb, the tables for User and Group are there and ready to be used.
Now, if you want to use these models in your other apps, you can import them, as you mention, with something like from django.contrib.auth.models import User. You can then do something like create a ForeignKey, OneToOneField or ManyToManyField on one of your models to the User model. When you do this, no tables are created (with the exception of ManyToManyField; more on that in a bit). The same table is always used for User, just as for any of your own models that you might create relationships between.
ManyToManyFields are slightly different in that an intermediary table is created (often called a "join table") that links both sides of the relationship together. However, this is purely for the purposes of that one particular relationship -- nothing about the actual User table is different or changed in any way.
The point is that one table is created for User and this same table is used to store all Users no matter what context they were created in. You can import User into any and all of your apps, create as many and as varied relationships as you like and nothing really changes as far as User is concerned.
If the table name or something else does not fit in your needs you can always just extend the User model.
from django.contrib.auth.models import User
class Employee(User):
...
Any class extending Model class in models.py contributes to database schema. That means, django search your (and also django core) model.py files and looks for any class that extends Model like:
some models.py
class SomeModel(Model):
...
...
class Otherthing(Model):
...
that is also applies for django core code files. Since all Database tables named using application label and model name, database ables created by django also have that...
For example,
from django.contrib.auth.models import User
If you track file hierarchy django -> contrib -> auth and open models.py file, you will see related model. Ther are also other Model classes in here, like Permission and Group models.
Since these models are under auth application, database tables are auth_user, auth_perission and auth_group
When you run manage.py syncdb command for the first time, django will create these tables...

How to externally populate a Django model?

What is the best idea to fill up data into a Django model from an external source?
E.g. I have a model Run, and runs data in an XML file, which changes weekly.
Should I create a view and call that view URL from a curl cronjob (with the advantage that that data can be read anytime, not only when the cronjob runs), or create a python script and install that script as a cron (with DJANGO _SETTINGS _MODULE variable setup before executing the script)?
There is excellent way to do some maintenance-like jobs in project environment- write a custom manage.py command. It takes all environment configuration and other stuff allows you to concentrate on concrete task.
And of course call it directly by cron.
You don't need to create a view, you should just trigger a python script with the appropriate Django environment settings configured. Then call your models directly the way you would if you were using a view, process your data, add it to your model, then .save() the model to the database.
I've used cron to update my DB using both a script and a view. From cron's point of view it doesn't really matter which one you choose. As you've noted, though, it's hard to beat the simplicity of firing up a browser and hitting a URL if you ever want to update at a non-scheduled interval.
If you go the view route, it might be worth considering a view that accepts the XML file itself via an HTTP POST. If that makes sense for your data (you don't give much information about that XML file), it would still work from cron, but could also accept an upload from a browser -- potentially letting the person who produces the XML file update the DB by themselves. That's a big win if you're not the one making the XML file, which is usually the case in my experience.
"create a python script and install that script as a cron (with DJANGO _SETTINGS _MODULE variable setup before executing the script)?"
First, be sure to declare your Forms in a separate module (e.g. forms.py)
Then, you can write batch loaders that look like this. (We have a LOT of these.)
from myapp.forms import MyObjectLoadForm
from myapp.models import MyObject
import xml.etree.ElementTree as ET
def xmlToDict( element ):
return dict(
field1= element.findtext('tag1'),
field2= element.findtext('tag2'),
)
def loadRow( aDict ):
f= MyObjectLoadForm( aDict )
if f.is_valid():
f.save()
def parseAndLoad( someFile ):
doc= ET.parse( someFile ).getroot()
for tag in doc.getiterator( "someTag" )
loadRow( xmlToDict(tag) )
Note that there is very little unique processing here -- it just uses the same Form and Model as your view functions.
We put these batch scripts in with our Django application, since it depends on the application's models.py and forms.py.
The only "interesting" part is transforming your XML row into a dictionary so that it works seamlessly with Django's forms. Other than that, this command-line program uses all the same Django components as your view.
You'll probably want to add options parsing and logging to make a complete command-line app out of this. You'll also notice that much of the logic is generic -- only the xmlToDict function is truly unique. We call these "Builders" and have a class hierarchy so that our Builders are all polymorphic mappings from our source documents to Python dictionaries.

Categories

Resources