Γράφοντας δικές σας django-admin
εντολές¶
Οι εφαρμογές μπορούν να κάνουν register τις δικές τους ενέργειες με το manage.py
. Για παράδειγμα, ίσως να θέλετε να προσθέσετε μια manage.py
ενέργεια για ένα Django app το οποίο προωθείτε για διανομή. Σε αυτό το άρθρο θα χτίσουμε μια δικιά μας εντολή, με το όνομα closepoll
, για την εφαρμογή polls
που φτιάξαμε στον οδηγό.
Για να το κάνουμε αυτό, απλά δημιουργήστε μια δομή φακέλων μέσα στην εφαρμογή ως εξής: management/commands
. Το Django θα κάνει register μια manage.py
εντολή για κάθε Python module μέσα σε αυτό το φάκελο του οποίου το όνομα δεν ξεκινά με τον χαρακτήρα της κάτω παύλας. Για παράδειγμα:
polls/
__init__.py
models.py
management/
__init__.py
commands/
__init__.py
_private.py
closepoll.py
tests.py
views.py
Αν χρησιμοποιείτε την Python 2, σιγουρευτείτε ότι το αρχείο __init__.py
(κενό αρχείο) υπάρχει και στους δύο φακέλους (management
και management/commands
) όπως φαίνεται παραπάνω αλλιώς η εντολή σας δεν θα μπορέσει να εντοπιστεί.
Σε αυτό το παράδειγμα, η εντολή closepoll
θα είναι διαθέσιμη σε κάθε project που έχει εγκατεστημένη αυτή την εφαρμογή, ήτοι, η εφαρμογή polls
βρίσκεται μέσα στη ρύθμιση INSTALLED_APPS
.
Το module _private.py
δεν θα είναι διαθέσιμο ως μια εντολή διαχείρισης (management command), αφού το όνομα του ξεκινά με την κάτω παύλα.
Το module closepoll.py
έχει μόνο μια απαίτηση – πρέπει να ορίσει μια κλάση Command
η οποία κάνει extend την κλάση BaseCommand
ή μια από τις subclasses της.
Αυτόνομα scripts
Οι παραμετροποιήσιμες εντολές διαχείρισης είναι εξαιρετικά χρήσιμες για την εκτέλεση αυτόνομων scripts ή για scripts τα οποία εκτελούνται σε περιοδική βάση από το crontab του UNIX ή από τα scheduled tasks των Windows.
Για να υλοποιήσετε την εντολή επεξεργαστείτε το αρχείο polls/management/commands/closepoll.py
για να δείχνει κάπως έτσι:
from django.core.management.base import BaseCommand, CommandError
from polls.models import Question as Poll
class Command(BaseCommand):
help = 'Closes the specified poll for voting'
def add_arguments(self, parser):
parser.add_argument('poll_id', nargs='+', type=int)
def handle(self, *args, **options):
for poll_id in options['poll_id']:
try:
poll = Poll.objects.get(pk=poll_id)
except Poll.DoesNotExist:
raise CommandError('Poll "%s" does not exist' % poll_id)
poll.opened = False
poll.save()
self.stdout.write(self.style.SUCCESS('Successfully closed poll "%s"' % poll_id))
Σημείωση
Όταν χρησιμοποιείτε εντολές διαχείρισης και θέλετε να εμφανίσετε κάτι στην κονσόλα, θα πρέπει να το στέλνετε στο self.stdout
και στο self.stderr
, αντί να το εκτυπώνετε απ’ ευθείας στο stdout
και stderr
. Χρησιμοποιώντας αυτά, το τεστ της εντολής σας γίνεται παιχνιδάκι. Σημειώστε, επίσης, ότι δεν χρειάζεται να προσθέτετε το χαρακτήρα της νέας γραμμής (line feed) στο τέλος κάθε self.stdout
ή self.stderr
του μηνύματος σας. Θα προστεθεί αυτόματα από το Django εκτός και αν θέλετε διαφορετικά χρησιμοποιώντας την παράμετρο ending
:
self.stdout.write("Unterminated line", ending='')
Την καινούργια σας εντολή μπορείτε να την καλείτε ως python manage.py closepoll <poll_id>
. Αν θέλετε να δείτε όλες τις διαθέσιμες εντολές, όλων των ενεργοποιημένων apps του project σας, πληκτρολογήστε python manage.py
και εκεί θα δείτε και τη δική σας κάτω από την εφαρμογή polls
.
Η μέθοδος handle()
λαμβάνει ένα ή περισσότερα poll_ids
(το <poll_id>
μέσα στο python manage.py closepoll <poll_id>
, που αν θέλουμε να προσθέσουμε πολλά τα διαχωρίζουμε με κενό) και θέτει το attribute poll.opened
σε False
για κάθε ένα poll. Αν ο χρήστης έχει πληκτρολογήσει κάποιο ή κάποια polls που δεν υπάρχουν, το σφάλμα CommandError
γίνεται raised. Το attribute poll.opened
δεν υπάρχει στον οδηγό και προστέθηκε στο μοντέλο polls.models.Question
για το συγκεκριμένο παράδειγμα. Δεν έχετε παρά να το προσθέσετε και εσείς, όμως μην ξεχάσετε να τρέξετε τις εντολές makemigrations
και migrate
για να προστεθεί η αντίστοιχη στήλη στη βάση δεδομένων σας.
Προαιρετικά arguments¶
Αλλάζοντας εύκολα την εντολή closepoll
ώστε να δέχεται επιπρόσθετες επιλογές όταν την καλείτε, θα μπορούσε να διαγράφει ένα poll αντί να το θέτει ως κλειστό (poll.opened = False
). Αυτές οι επιλογές μπορούν να προστεθούν στη μέθοδο add_arguments()
ως εξής:
class Command(BaseCommand):
def add_arguments(self, parser):
# Positional arguments
parser.add_argument('poll_id', nargs='+', type=int)
# Named (optional) arguments
parser.add_argument(
'--delete',
action='store_true',
dest='delete',
default=False,
help='Delete poll instead of closing it',
)
def handle(self, *args, **options):
# ...
if options['delete']:
poll.delete()
# ...
Η επιλογή (η delete
στο παράδειγμα μας) είναι διαθέσιμη μέσα στη μέθοδο handle μέσα από το options dictionary με το key delete. Δείτε περισσότερα στο εγχειρίδιο της Python σχετικά με τη χρήση της argparse
και τη μέθοδο add_argument
.
Πέρα από τη δυνατότητα της πρόσθεσης δικών σας επιλογών στην εντολή, όλες οι εντολές διαχείρισης μπορούν να δεχτούν μερικές δικές τους, προεγκατεστημένες, επιλογές όπως η --verbosity
και η --traceback
.
Εντολές διαχείρισης και locales¶
Από προεπιλογή, η μέθοδος BaseCommand.execute()
απενεργοποιεί τις μεταφράσεις (που τυχόν έχουν γίνει) επειδή κάποιες εντολές διαχείρισης του Django εκτελούν διάφορες εργασίες (πχ population της database, rendering των δεδομένων που βλέπει ο χρήστης) οι οποίες απαιτούν μια γλώσσα που να είναι καθολική και να μην εξαρτάται από το εκάστοτε project του προγραμματιστή.
Αν, για κάποιο λόγο, η δική σας εντολή διαχείρισης χρειάζεται να χρησιμοποιήσει ένα μόνιμο locale, θα χρειαστεί να το ενεργοποιήσετε/απενεργοποιήσετε χειροκίνητα μέσα στη μέθοδο handle()
χρησιμοποιώντας τις μεθόδους που παρέχονται από το module I18N:
from django.core.management.base import BaseCommand, CommandError
from django.utils import translation
class Command(BaseCommand):
...
can_import_settings = True
def handle(self, *args, **options):
# Activate a fixed locale, e.g. Russian
translation.activate('ru')
# Or you can activate the LANGUAGE_CODE # chosen in the settings:
from django.conf import settings
translation.activate(settings.LANGUAGE_CODE)
# Your command logic here
...
translation.deactivate()
Μια άλλη προσέγγιση ίσως, είναι να χρησιμοποιήσετε το locale που έχετε θέσει στα settings του project σας και το Django να μην το απενεργοποιήσει. Το επιτυγχάνετε αυτό χρησιμοποιώντας την επιλογή BaseCommand.leave_locale_alone
.
Όταν δουλεύετε σε σενάρια παρόμοια με τα παραπάνω, λάβετε υπόψη σας ότι οι εντολές διαχείρισης του συστήματος πρέπει να είναι πολύ προσεκτικές όταν τρέχουν σε μη ομοιόμορφα locales, που σημαίνει ότι θα χρειαστεί να:
Σιγουρευτείτε ότι η ρύθμιση
USE_I18N
είναι πάνταTrue
όταν τρέχετε την εντολή (αυτό είναι ένα καλό παράδειγμα των πιθανών προβλημάτων που απορρέουν από ένα δυναμικό runtime περιβάλλον, όπου οι Django εντολές διαχείρισης προσπαθούν να αποφύγουν απενεργοποιώντας τις μεταφράσεις).Ελέγξτε τον κώδικα της εντολής σας καθώς και τυχόν κώδικα που καλεί η εντολή σας για να δείτε τυχόν διαφορές συμπεριφοράς όταν τα locales αλλάζουν και να αξιοποιήσετε την επιρροή του κώδικα πάνω στην προβλέψιμη συμπεριφορά της εντολής σας.
Τεστ¶
Πληροφορίες στο πως να τεστάρετε τις δικές σας εντολές διαχείρισης, μπορείτε να βρείτε στα εργαλεία για τεστ.
Objects εντολών¶
Η κύρια κλάση από την οποία πηγάζουν όλες οι εντολές διαχείρισης.
Χρησιμοποιήστε αυτή την κλάση αν θέλετε να έχετε πρόσβαση σε όλους τους μηχανισμούς οι οποίοι μεταφράζουν τις επιλογές στη γραμμή εντολών (όπως την --delete
που χρησιμοποιήσαμε νωρίτερα) και αποφασίζουν, ανάλογα την επιλογή, ποιον κώδικα να καλέσουν περαιτέρω. Αν δεν θέλετε να αλλάξετε κάποια από αυτές τις συμπεριφορές, ίσως σας φανεί χρήσιμη μια από τις subclasses της BaseCommand
.
Πραγματοποιώντας subclassing στην κλάση BaseCommand
απαιτεί να υλοποιήσετε τη μέθοδο handle()
.
Attributes¶
Όλα τα attributes μπορούν να οριστούν στη δική σας κλάση και να χρησιμοποιηθούν στις subclasses της BaseCommand
.
-
BaseCommand.
can_import_settings
¶ Τύπου boolean που υποδεικνύει αν η εντολή μπορεί να κάνει import τα Django settings. Αν είναι
True
, τότε η μέθοδοςexecute()
θα επιβεβαιώσει ότι αυτό είναι δυνατόν πριν προχωρήσει. Προεπιλεγμένη τιμή είναι τοTrue
.
-
BaseCommand.
help
¶ Μια μικρή περιγραφή της εντολής η οποία θα εκτυπωθεί στην κονσόλα όταν ο χρήστης τρέξει την εντολή
python manage.py help <command>
.
-
BaseCommand.
missing_args_message
¶ Αν η εντολή σας ορίζει κάποια υποχρεωτικά positional arguments, μπορείτε να παραμετροποιήσετε το μήνυμα σφάλματος που επιστρέφεται, σε περίπτωση που αυτά τα arguments λείπουν. Προεπιλεγμένη τιμή είναι η έξοδος της
argparse
(“too few arguments”).
-
BaseCommand.
output_transaction
¶ Τύπου boolean που υποδεικνύει αν η εντολή θα εκτυπώσει (στην κονσόλα) εντολές SQL. Αν είναι
True
, τότε η έξοδος θα συμπεριληφθεί μέσα σε έναBEGIN;
καιCOMMIT;
. Προεπιλεγμένη τιμή είναι τοFalse
.
-
BaseCommand.
requires_migrations_checks
¶ - New in Django 1.10.
Τύπου boolean. Αν είναι
True
, η εντολή θα εκτυπώσει ένα warning αν το σετ των migrations στο δίσκο δεν ταιριάζει με τα migrations στη βάση δεδομένων. Υπόψιν ότι, ένα warning δεν εμποδίζει την εντολή από το να εκτελεστεί. Προεπιλεγμένη τιμή είναι τοFalse
.
-
BaseCommand.
requires_system_checks
¶ Τύπου boolean. Αν είναι
True
, τότε ολόκληρο το Django project σας θα ελεγχθεί για τυχόν σφάλματα πριν εκτελεστεί η εντολή. Προεπιλεγμένη τιμή είναι τοTrue
.
-
BaseCommand.
leave_locale_alone
¶ Τύπου boolean που υποδεικνύει αν το locale που έχει οριστεί στα settings του project σας θα πρέπει να ληφθεί υπόψιν κατά τη διάρκεια εκτέλεσης της εντολής, αντί να χρησιμοποιηθεί η τιμή ‘en-us’.
Προεπιλεγμένη τιμή είναι το
False
.Σιγουρευτείτε ότι γνωρίζετε τι κάνετε όταν αποφασίσετε να αλλάξετε την τιμή αυτής της επιλογής αν η django-admin εντολή σας δημιουργεί περιεχόμενο στη βάση δεδομένων το οποίο εξαρτάται από τις τοπικές ρυθμίσεις γλώσσας (locale). Το περιεχόμενο αυτό θα πρέπει να μην περιέχει τυχόν μεταφράσεις (όπως γίνεται πχ με το module των permissions
django.contrib.auth
) καθώς τυχόν διαφορά του locale με το de facto προεπιλεγμένο ‘en-us’ μπορεί να προκαλέσει ακούσιες επιπτώσεις. Δείτε περισσότερα στο τμήμα Εντολές διαχείρισης και locales παραπάνω, για περισσότερες λεπτομέρειες.Αυτή η επιλογή δεν μπορεί να είναι
False
όταν η επιλογήcan_import_settings
είναι, επίσης,False
επειδή η απόπειρα να οριστεί το locale χρειάζεται πρόσβαση στα settings. Σε αυτή την περίπτωση θα γίνει raise έναCommandError
.
-
BaseCommand.
style
¶ Ένα attribute που βοηθά στο να χρωματίσει την έξοδο (δηλαδή το κείμενο) όταν γράφετε στο
self.stdout
ή στοself.stderr
. Για παράδειγμα:self.stdout.write(self.style.SUCCESS('...'))
Δείτε στο Syntax coloring για να μάθετε πως να τροποποιείτε την παλέτα των χρωμάτων και να δείτε τα διαθέσιμα στυλ (χρήση της έκδοσης των κεφαλαίων γραμμάτων των “ρόλων” όπως περιγράφονται σε αυτή την ενότητα).
Αν δηλώσετε την επιλογή
--no-color
όταν τρέχετε την εντολή σας, όλες οι κλήσεις τηςself.style()
θα επιστρέψουν το αρχικό string χωρίς κάποιο χρώμα.
Μέθοδοι¶
Η κλάση BaseCommand
έχει μερικές μεθόδους οι οποίες μπορούν να παρακαμφθούν (overridden) αλλά μόνο η μέθοδος handle()
πρέπει να υλοποιηθεί υποχρεωτικά.
Υλοποίηση ενός constructor μέσα σε μια subclass
Αν υλοποιήσετε την __init__
μέσα σε μια subclass της BaseCommand
θα πρέπει πρώτα να καλέσετε την __init__
της κλάσης BaseCommand
, όπως φαίνεται παρακάτω:
class Command(BaseCommand):
def __init__(self, *args, **kwargs):
super(Command, self).__init__(*args, **kwargs)
# ...
-
BaseCommand.
add_arguments
(parser)[πηγή]¶ Σημείο εισόδου για να προσθέσετε arguments στο μεταφραστή (parser) για τον χειρισμό των arguments που εισάγονται στη γραμμή εντολών (όπως το argument
--delete
που αναφέραμε παραπάνω). Τυχόν δικές σας διαχειριστικές εντολές θα πρέπει να παρακάμπτουν αυτή τη μέθοδο για να προσθέσουν positional και optional arguments που θα δέχεται η εντολή. Η κλήση τηςsuper()
δεν είναι απαραίτητη όταν κάνετε απ’ ευθείας subclass την κλάσηBaseCommand
.
-
BaseCommand.
get_version
()[πηγή]¶ Επιστρέφει την έκδοση του Django, η οποία θα πρέπει να είναι η σωστή για όλες τις προεγκατεστημένες εντολές του Django. Τυχόν δικές σας διαχειριστικές εντολές μπορούν να παρακάμψουν αυτή τη μέθοδο για να επιστρέφουν τη δική τους έκδοση.
-
BaseCommand.
execute
(*args, **options)[πηγή]¶ Προσπαθεί να εκτελέσει την εντολή που γράψατε, πραγματοποιεί τυχόν τεστ που αφορά το σύστημα (ανάλογα την τιμή του attribute
requires_system_checks
). Αν η εντολή κάνει raise το exceptionCommandError
, τότε θα εμφανιστεί ανάλογο σφάλμα στοstderr
.
Καλώντας μια εντολή διαχείρισης μέσα από τον κώδικα σας
Η μέθοδος execute()
δεν θα πρέπει να καλείται απ’ ευθείας από τον κώδικα σας για την εκτέλεση μιας εντολής. Αντί αυτής, χρησιμοποιήστε τη συνάρτηση call_command()
.
-
BaseCommand.
handle
(*args, **options)[πηγή]¶ Η πραγματική λογική της εντολής. Όλες οι subclasses πρέπει να υλοποιήσουν αυτή τη μέθοδο.
Μπορεί να επιστρέψει ένα Unicode string το οποίο θα εκτυπωθεί στο
stdout
(συμπεριλαμβανόμενο μέσα σταBEGIN;
καιCOMMIT;
, αν το attributeoutput_transaction
είναιTrue
).
-
BaseCommand.
check
(app_configs=None, tags=None, display_num_errors=False)[πηγή]¶ Χρησιμοποιεί το σύστημα ελέγχου του framework για να επιθεωρήσει ολόκληρο το Django project για τυχόν προβλήματα. Τα σοβαρά προβλήματα γίνονται raised ως
CommandError
. Τα warnings εκτυπώνονται στοstderr
. Τυχόν δευτερεύουσες ειδοποιήσεις εκτυπώνονται στοstdout
.Αν τα ορίσματα
app_configs
καιtags
είναι και τα δύο τύπουNone
, τότε θα γίνουν όλα τα συστήματα ελέγχου. Το όρισμαtags
μπορεί να είναι μια λίστα από check tags, όπωςcompatibility
ήmodels
.
BaseCommand
subclasses¶
-
class
AppCommand
¶
Μια εντολή διαχείρισης η οποία δέχεται ως arguments (όπως το <poll_id>
στην εντολή που γράψαμε παραπάνω python manage.py closepoll <poll_id>
) ένα ή περισσότερα labels εφαρμογών και κάνει κάτι με καθένα από αυτά.
Αντί της υλοποίησης της μεθόδου handle()
, οι subclasses πρέπει να υλοποιούν τη μέθοδο handle_app_config()
η οποία θα καλείται μια φορά για κάθε εφαρμογή.
-
AppCommand.
handle_app_config
(app_config, **options)¶ Εκτελεί τις ενέργειες της εντολής για το
app_config
το οποίο θα είναι ένα instance της κλάσηςAppConfig
που αντιστοιχεί στο label της εφαρμογής που δόθηκε στη γραμμή εντολών.
-
class
LabelCommand
¶
Μια εντολή διαχείρισης η οποία δέχεται ένα η περισσότερα arguments (labels) μέσω της γραμμής εντολών και κάνει κάτι με καθένα από αυτά.
Αντί της υλοποίησης της μεθόδου handle()
, οι subclasses πρέπει να υλοποιούν τη μέθοδο handle_label()
, η οποία θα καλείται μια φορά για κάθε label.
-
LabelCommand.
label
¶ Ένα αλφαριθμητικό (string) που περιγράφει τις παραμέτρους που δόθηκαν στην εντολή. Το αλφαριθμητικό χρησιμοποιείται στα μηνύματα σφαλμάτων καθώς και στο κείμενο περιγραφής της εντολής. Προεπιλογή είναι το
'label'
.
-
LabelCommand.
handle_label
(label, **options)¶ Εκτελεί τις ενέργειες της εντολής για το
label
, το οποίο θα είναι ένα string όπως δόθηκε στη γραμμή εντολών.
Exceptions εντολής¶
Μια κλάση Exception που υποδεικνύει ότι προέκυψε κάποιο πρόβλημα κατά τη διάρκεια εκτέλεσης της εντολής διαχείρισης.
Αν αυτό το exception γίνει raised κατά τη διάρκεια της εκτέλεσης, μέσω της κονσόλας, μιας διαχειριστικής εντολής, θα “πιαστεί” (caught) και θα μετατραπεί σε ένα όμορφα εκτυπώσιμο μήνυμα σφάλματος το οποίο θα προωθηθεί στην ανάλογη έξοδο προβολής (πχ stderr, stdout κλπ). Επομένως, ο καλύτερος τρόπος για να υποδείξετε ότι κάτι πήγε στραβά κατά τη διάρκεια της εκτέλεσης της εντολής σας, είναι κάνοντας raise αυτό το exception (με μια λογική περιγραφή του σφάλματος που προέκυψε, φυσικά).
Αν η εντολή διαχείρισης καλείται μέσα από τον κώδικα σας μέσω της συνάρτησης call_command()
, τότε είναι δικό σας θέμα να κάνετε catch, όποτε το κρίνετε απαραίτητο, αυτό το exception.