Django
Modelli
Ricerca…
introduzione
Nel caso base, un modello è una classe Python che esegue il mapping su una singola tabella di database. Gli attributi della mappa della classe alle colonne nella tabella e un'istanza della classe rappresentano una riga nella tabella del database. I modelli ereditano da django.db.models.Model
che fornisce una ricca API per aggiungere e filtrare i risultati dal database.
Creare il tuo primo modello
I modelli sono in genere definiti nel file models.py
nella sottodirectory dell'applicazione. La classe Model
del modulo django.db.models
è una buona classe iniziale per estendere i tuoi modelli. Per esempio:
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey('Author', on_delete=models.CASCADE, related_name='authored_books')
publish_date = models.DateField(null=True, blank=True)
def __str__(self): # __unicode__ in python 2.*
return self.title
Ogni attributo in un modello rappresenta una colonna nel database.
-
title
è un testo con una lunghezza massima di 100 caratteri -
author
è unForeignKey
che rappresenta una relazione con un altro modello / tabella, in questo casoAuthor
(usato solo per scopi di esempio).on_delete
dice al database cosa fare con l'oggetto se l'oggetto correlato (unAuthor
) viene cancellato. (Va notato che dal momento che django 1.9on_delete
può essere utilizzato come secondo argomento posizionale, in django 2 è un argomento obbligatorio ed è consigliabile trattarlo come tale immediatamente. Nelle versioni precedenti verràon_delete
come predefinito suCASCADE
.) -
publish_date
memorizza una data. Sianull
cheblank
sono impostati suTrue
per indicare che non si tratta di un campo obbligatorio (ovvero è possibile aggiungerlo in un secondo momento o lasciarlo vuoto).
Insieme agli attributi definiamo un metodo __str__
questo restituisce il titolo del libro che verrà usato come rappresentazione di string
dove necessario, piuttosto che come predefinito.
Applicazione delle modifiche al database (Migrazioni)
Dopo aver creato un nuovo modello o modificato i modelli esistenti, sarà necessario generare migrazioni per le modifiche e quindi applicare le migrazioni al database specificato. Questo può essere fatto usando il sistema di migrazione integrato di Django. Utilizzo dell'utilità manage.py
nella directory root del progetto:
python manage.py makemigrations <appname>
Il comando precedente creerà gli script di migrazione necessari nella sottodirectory delle migrations
dell'applicazione. Se si omette il parametro <appname>
, verranno elaborate tutte le applicazioni definite nell'argomento INSTALLED_APPS
di settings.py
. Se lo ritieni necessario, puoi modificare le migrazioni.
È possibile controllare quali migrazioni sono richieste senza effettivamente creare la migrazione utilizzare l'opzione --dry-run, ad esempio:
python manage.py makemigrations --dry-run
Per applicare le migrazioni:
python manage.py migrate <appname>
Il comando precedente eseguirà gli script di migrazione generati nel primo passaggio e aggiornerà fisicamente il database.
Se il modello del database esistente viene modificato, è necessario il seguente comando per apportare le modifiche necessarie.
python manage.py migrate --run-syncdb
Django creerà la tabella con il nome <appname>_<classname>
per impostazione predefinita. A volte non vuoi usarlo. Se vuoi cambiare il nome predefinito, puoi annunciare il nome della tabella impostando db_table
nella classe Meta
:
from django.db import models
class YourModel(models.Model):
parms = models.CharField()
class Meta:
db_table = "custom_table_name"
Se vuoi vedere quale codice SQL verrà eseguito da una certa migrazione, esegui questo comando:
python manage.py sqlmigrate <app_label> <migration_number>
Django> 1.10
La nuova opzione makemigrations --check
rende il comando di uscita con uno stato diverso da zero quando vengono rilevate modifiche del modello senza migrazioni.
Vedi Migrazioni per maggiori dettagli sulle migrazioni.
Creare un modello con relazioni
Relazione molti-a-uno
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=50)
#Book has a foreignkey (many to one) relationship with author
class Book(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
publish_date = models.DateField()
Opzione più generica. Può essere utilizzato ovunque tu voglia rappresentare una relazione
Relazione molti-a-molti
class Topping(models.Model):
name = models.CharField(max_length=50)
# One pizza can have many toppings and same topping can be on many pizzas
class Pizza(models.Model):
name = models.CharField(max_length=50)
toppings = models.ManyToManyField(Topping)
Internamente questo è rappresentato tramite un'altra tabella. E ManyToManyField
dovrebbe essere messo su modelli che saranno modificati su un modulo. Ad esempio: l' Appointment
avrà un ManyToManyField
chiamato Customer
, Pizza
ha Toppings
e così via.
Relazione molti-a-molti utilizzando le classi Through
class Service(models.Model):
name = models.CharField(max_length=35)
class Client(models.Model):
name = models.CharField(max_length=35)
age = models.IntegerField()
services = models.ManyToManyField(Service, through='Subscription')
class Subscription(models.Model):
client = models.ForeignKey(Client)
service = models.ForeignKey(Service)
subscription_type = models.CharField(max_length=1, choices=SUBSCRIPTION_TYPES)
created_at = models.DateTimeField(default=timezone.now)
In questo modo, possiamo effettivamente mantenere più metadati su una relazione tra due entità. Come si può vedere, un cliente può essere abbonato a diversi servizi tramite diversi tipi di abbonamento. L'unica differenza in questo caso è che per aggiungere nuove istanze alla relazione M2M, non è possibile utilizzare il metodo di scelta rapida pizza.toppings.add(topping)
, invece, dovrebbe essere creato un nuovo oggetto della classe through , Subscription.objects.create(client=client, service=service, subscription_type='p')
In altre lingue, le
through tables
sono anche note comeJoinColumn
,Intersection table
omapping table
Relazione uno-a-uno
class Employee(models.Model):
name = models.CharField(max_length=50)
age = models.IntegerField()
spouse = models.OneToOneField(Spouse)
class Spouse(models.Model):
name = models.CharField(max_length=50)
Usa questi campi quando avrai sempre una relazione di composizione tra i due modelli.
Query di base su Django DB
Django ORM è un'astrazione potente che ti consente di archiviare e recuperare dati dal database senza dover scrivere query SQL.
Assumiamo i seguenti modelli:
class Author(models.Model):
name = models.CharField(max_length=50)
class Book(models.Model):
name = models.CharField(max_length=50)
author = models.ForeignKey(Author)
Supponendo che tu abbia aggiunto il codice sopra a un'applicazione django ed esegui il comando migrate
(in modo che il tuo database sia creato). Inizia la shell di Django di
python manage.py shell
Questo avvia la shell python standard ma con importate librerie Django rilevanti, in modo che tu possa concentrarti direttamente sulle parti importanti.
Inizia importando i modelli che abbiamo appena definito (presumo che ciò avvenga in un file models.py
)
from .models import Book, Author
Esegui la tua prima query di selezione:
>>> Author.objects.all()
[]
>>> Book.objects.all()
[]
Consente di creare un autore e un oggetto libro:
>>> hawking = Author(name="Stephen hawking")
>>> hawking.save()
>>> history_of_time = Book(name="history of time", author=hawking)
>>> history_of_time.save()
oppure utilizzare la funzione crea per creare oggetti modello e salvare in un codice di linea
>>> wings_of_fire = Book.objects.create(name="Wings of Fire", author="APJ Abdul Kalam")
Ora consente di eseguire la query
>>> Book.objects.all()
[<Book: Book object>]
>>> book = Book.objects.first() #getting the first book object
>>> book.name
u'history of time'
Aggiungiamo una clausola where alla nostra query select
>>> Book.objects.filter(name='nothing')
[]
>>> Author.objects.filter(name__startswith='Ste')
[<Author: Author object>]
Per ottenere i dettagli sull'autore di un determinato libro
>>> book = Book.objects.first() #getting the first book object
>>> book.author.name # lookup on related model
u'Stephen hawking'
Per ottenere tutti i libri pubblicati da Stephen Hawking (libro Lookup dal suo autore)
>>> hawking.book_set.all()
[<Book: Book object>]
_set
è la notazione usata per "Reverse lookups" cioè, mentre il campo di ricerca è sul modello Book, possiamo usare book_set
su un oggetto autore per ottenere tutti i suoi libri.
Un tavolo non gestito di base.
Ad un certo punto del tuo utilizzo di Django, potresti trovarti a voler interagire con le tabelle che sono già state create o con le viste del database. In questi casi, non vorrai che Django gestisca le tabelle attraverso le sue migrazioni. Per impostare questo, è necessario aggiungere solo una variabile alla classe Meta
del modello: managed = False
.
Ecco un esempio di come è possibile creare un modello non gestito per interagire con una vista del database:
class Dummy(models.Model):
something = models.IntegerField()
class Meta:
managed = False
Questo può essere associato a una vista definita in SQL come segue.
CREATE VIEW myapp_dummy AS
SELECT id, something FROM complicated_table
WHERE some_complicated_condition = True
Una volta creato questo modello, puoi utilizzarlo come faresti con qualsiasi altro modello:
>>> Dummy.objects.all()
[<Dummy: Dummy object>, <Dummy: Dummy object>, <Dummy: Dummy object>]
>>> Dummy.objects.filter(something=42)
[<Dummy: Dummy object>]
Modelli avanzati
Un modello può fornire molte più informazioni rispetto ai soli dati su un oggetto. Vediamo un esempio e scomporlo in ciò che è utile per:
from django.db import models
from django.urls import reverse
from django.utils.encoding import python_2_unicode_compatible
@python_2_unicode_compatible
class Book(models.Model):
slug = models.SlugField()
title = models.CharField(max_length=128)
publish_date = models.DateField()
def get_absolute_url(self):
return reverse('library:book', kwargs={'pk':self.pk})
def __str__(self):
return self.title
class Meta:
ordering = ['publish_date', 'title']
Chiave primaria automatica
Potresti notare l'uso di self.pk
nel metodo get_absolute_url
. Il campo pk
è un alias della chiave primaria di un modello. Inoltre, Django aggiungerà automaticamente una chiave primaria se manca. Questa è una cosa in meno di cui preoccuparsi e ti consente di impostare la chiave esterna per qualsiasi modello e ottenerli facilmente.
URL assoluto
La prima funzione definita è get_absolute_url
. In questo modo, se si dispone di un libro, è possibile ottenere un collegamento ad esso senza manipolare il tag url, risolvere, attributo e simili. Chiama semplicemente book.get_absolute_url
e ottieni il link giusto. Come bonus, il tuo oggetto nell'amministratore di django otterrà un pulsante "Visualizza sul sito".
Rappresentazione delle stringhe
Avere un metodo __str__
consente di utilizzare l'oggetto quando è necessario visualizzarlo. Ad esempio, con il metodo precedente, aggiungere un link al libro in un modello è semplice come <a href="{{ book.get_absolute_url }}">{{ book }}</a>
. Dritto al punto. Questo metodo controlla anche ciò che viene visualizzato nel menu a discesa dell'amministratore, ad esempio per la chiave esterna.
Il decoratore di classi ti consente di definire il metodo una volta sia per __str__
che __unicode__
su python 2 senza causare alcun problema su python 3. Se ti aspetti che la tua app funzioni su entrambe le versioni, questa è la strada da percorrere.
Campo di lumaca
Il campo slug è simile a un campo char ma accetta meno simboli. Per impostazione predefinita, solo lettere, numeri, caratteri di sottolineatura o trattini. È utile se vuoi identificare un oggetto usando una bella rappresentazione, ad esempio nell'URL.
La classe Meta
La classe Meta
ci consente di definire molte più informazioni sull'intera collezione di oggetti. Qui è impostato solo l'ordine predefinito. Ad esempio, è utile con l'oggetto ListView. Prende una lista ideale di campo da usare per l'ordinamento. Qui, il libro verrà ordinato prima per data di pubblicazione e poi per titolo se la data è la stessa.
Gli altri attributi di frequenza sono verbose_name
e verbose_name_plural
. Di default, sono generati dal nome del modello e dovrebbero andare bene. Ma la forma plurale è ingenua, semplicemente aggiungendo un 's' al singolare, quindi potresti volerlo impostare in modo esplicito in alcuni casi.
Valori calcolati
Una volta che un oggetto modello è stato recuperato, diventa un'istanza pienamente realizzata della classe. Di conseguenza, è possibile accedere a qualsiasi metodo aggiuntivo nei moduli e nei serializzatori (come Django Rest Framework).
L'utilizzo delle proprietà python è un modo elegante per rappresentare valori aggiuntivi che non sono memorizzati nel database a causa di circostanze variabili.
def expire():
return timezone.now() + timezone.timedelta(days=7)
class Coupon(models.Model):
expiration_date = models.DateField(default=expire)
@property
def is_expired(self):
return timezone.now() > self.expiration_date
Mentre nella maggior parte dei casi è possibile integrare dati con annotazioni sui propri querysets, i valori calcolati come proprietà del modello sono ideali per i calcoli che non possono essere valutati semplicemente nell'ambito di una query.
Inoltre, le proprietà, dal momento che sono dichiarate sulla classe python e non come parte dello schema, non sono disponibili per l'esecuzione di query.
Aggiunta di una rappresentazione di stringa di un modello
Per creare una presentazione leggibile dall'uomo di un oggetto modello è necessario implementare il metodo Model.__str__()
(o Model.__unicode__()
su python2). Questo metodo verrà chiamato ogni volta che si chiama str()
su un'istanza del modello (incluso, ad esempio, quando il modello viene utilizzato in un modello). Ecco un esempio:
Crea un modello di libro.
# your_app/models.py from django.db import models class Book(models.Model): name = models.CharField(max_length=50) author = models.CharField(max_length=50)
Crea un'istanza del modello e salvala nel database:
>>> himu_book = Book(name='Himu Mama', author='Humayun Ahmed') >>> himu_book.save()
Esegui
print()
sull'istanza:>>> print(himu_book) <Book: Book object>
<Book: Book object> , l'output predefinito, non ci è di aiuto. Per risolvere questo problema, aggiungiamo un metodo __str__
.
from django.utils.encoding import python_2_unicode_compatible
@python_2_unicode_compatible
class Book(models.Model):
name = models.CharField(max_length=50)
author = models.CharField(max_length=50)
def __str__(self):
return '{} by {}'.format(self.name, self.author)
Nota che il decoratore python_2_unicode_compatible
è necessario solo se vuoi che il tuo codice sia compatibile con python 2. Questo decoratore copia il metodo __str__
per creare un metodo __unicode__
. django.utils.encoding
da django.utils.encoding
.
Ora se chiamiamo nuovamente la funzione di stampa l'istanza del libro:
>>> print(himu_book)
Himu Mama by Humayun Ahmed
Molto meglio!
La rappresentazione della stringa viene anche utilizzata quando il modello viene utilizzato in un campo ModelForm
per i campi ForeignKeyField
e ManyToManyField
.
Model mixins
Negli stessi casi, diversi modelli potrebbero avere gli stessi campi e le stesse procedure nel ciclo di vita del prodotto. Per gestire queste somiglianze senza avere l'ereditarietà di ripetizione del codice potrebbe essere utilizzato. Invece di ereditare un'intera classe, il modello di progettazione mixin ci consente di ereditare ( o alcuni include include ) alcuni metodi e attributi. Vediamo un esempio:
class PostableMixin(models.Model):
class Meta:
abstract=True
sender_name = models.CharField(max_length=128)
sender_address = models.CharField(max_length=255)
receiver_name = models.CharField(max_length=128)
receiver_address = models.CharField(max_length=255)
post_datetime = models.DateTimeField(auto_now_add=True)
delivery_datetime = models.DateTimeField(null=True)
notes = models.TextField(max_length=500)
class Envelope(PostableMixin):
ENVELOPE_COMMERCIAL = 1
ENVELOPE_BOOKLET = 2
ENVELOPE_CATALOG = 3
ENVELOPE_TYPES = (
(ENVELOPE_COMMERCIAL, 'Commercial'),
(ENVELOPE_BOOKLET, 'Booklet'),
(ENVELOPE_CATALOG, 'Catalog'),
)
envelope_type = models.PositiveSmallIntegerField(choices=ENVELOPE_TYPES)
class Package(PostableMixin):
weight = models.DecimalField(max_digits=6, decimal_places=2)
width = models.DecimalField(max_digits=5, decimal_places=2)
height = models.DecimalField(max_digits=5, decimal_places=2)
depth = models.DecimalField(max_digits=5, decimal_places=2)
Per trasformare un modello in una classe astratta, dovrai menzionare abstract=True
nella sua classe Meta
interna. Django non crea tabelle per i modelli astratti nel database. Tuttavia, per i modelli Envelope
e Package
, le tabelle corrispondenti verranno create nel database.
Inoltre i campi alcuni metodi modello saranno necessari in più di un modello. Quindi questi metodi potrebbero essere aggiunti ai mixin per prevenire la ripetizione del codice. Ad esempio, se creiamo un metodo per impostare la data di consegna su PostableMixin
, sarà accessibile da entrambi i suoi figli:
class PostableMixin(models.Model):
class Meta:
abstract=True
...
...
def set_delivery_datetime(self, dt=None):
if dt is None:
from django.utils.timezone import now
dt = now()
self.delivery_datetime = dt
self.save()
Questo metodo potrebbe essere usato come segue sui bambini:
>> envelope = Envelope.objects.get(pk=1)
>> envelope.set_delivery_datetime()
>> pack = Package.objects.get(pk=1)
>> pack.set_delivery_datetime()
Chiave primaria UUID
Un modello per impostazione predefinita utilizza una chiave primaria con incremento automatico (numero intero). Questo ti darà una sequenza di chiavi 1, 2, 3.
Diversi tipi di chiavi primarie possono essere impostati su un modello con piccole modifiche al modello.
Un UUID è un identificatore univoco universale, si tratta di un identificatore casuale a 32 caratteri che può essere utilizzato come ID. Questa è una buona opzione da utilizzare quando non si desidera che ID sequenziali siano assegnati ai record nel proprio database. Se usato su PostgreSQL, questo memorizza in un tipo di dati uuid, altrimenti in un char (32).
import uuid
from django.db import models
class ModelUsingUUID(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
La chiave generata sarà nel formato 7778c552-73fc-4bc4-8bf9-5a2f6f7b7f47
Eredità
L'ereditarietà tra i modelli può essere effettuata in due modi:
- una classe astratta comune (vedi l'esempio "Model mixins")
- un modello comune con più tabelle
L'ereditarietà di più tabelle creerà una tabella per i campi comuni e uno per esempio di modello figlio:
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
class Restaurant(Place):
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
creerà 2 tabelle, una per il Place
e una per il Restaurant
con un campo OneToOne
nascosto da Place
per i campi comuni.
si noti che questo richiederà una query aggiuntiva alle tabelle dei luoghi ogni volta che si recupera un oggetto ristorante.