Festlegen des Standardwerts für das Fremdschlüsselattribut

75

Was ist der beste Weg, um einen Standardwert für ein Fremdschlüsselfeld in einem Modell festzulegen? Angenommen, ich habe zwei Modelle Studentund einen ExamSchüler exam_takenals Fremdschlüssel. Wie würde ich idealerweise einen Standardwert dafür festlegen? Hier ist ein Protokoll meiner Bemühungen

class Student(models.Model):
   ....
   .....
   exam_taken = models.ForeignKey("Exam", default=1)

Funktioniert, aber ahnen Sie, es gibt einen besseren Weg.

def get_exam():
    return Exam.objects.get(id=1)

class Student(models.Model):
    ....
    .....
    exam_taken = models.ForeignKey("Exam", default=get_exam)

Dies schlägt jedoch fehl, da bei der Synchronisierung kein Fehler vorliegt.

Jede Hilfe wäre dankbar.

Ursprünglicher Pappachan
quelle
Wie wäre es mit: stackoverflow.com/questions/937954/…
Nitzan Tomer
@NitzanTomer Es für ein AdminModelField. Hatte es vorher gesehen.
Primal Pappachan
5
@TomIngram: default=get_exam()ruft get_examsofort und den Wert fest, während default=get_examspeichert die Methode , die später jedes Mal aufgerufen werden , würde das defaultAttribut verwendet wird, um den Wert zu diesem Zeitpunkt zu erhalten. Es wird oft mit Datetime verwendet, das heißt default=datetime.now, nicht default=datetime.now() .
Chris Pratt
@ TomIngram: Ich diskutiere nicht über die Vorzüge eines Ansatzes gegenüber einem anderen. Mein Punkt war nur, dass es gültig ist, und der Autor scheint es so zu wollen.
Chris Pratt
1
@ TomIngram: Der Unterschied besteht darin, dass das Hinzufügen der Klammer nicht mehr "aufrufbar" ist. Es ist ein statischer Wert, nicht anders als nur eine ganze Zahl dort zu setzen.
Chris Pratt

Antworten:

43

In beiden Beispielen codieren Sie die ID der Standardinstanz fest. Wenn das unvermeidlich ist, würde ich einfach eine Konstante setzen.

DEFAULT_EXAM_ID = 1
class Student(models.Model):
    ...
    exam_taken = models.ForeignKey("Exam", default=DEFAULT_EXAM_ID)

Weniger Code und die Benennung der Konstante machen sie besser lesbar.

Gareth
quelle
Ich erhalte folgende Fehlermeldung: TypeError: Additional arguments should be named <dialectname>_<argument>, got 'default' Mit Code: category_id = db.Column(db.Integer, db.ForeignKey("category.id", default=1), primary_key=True) Können Sie bitte helfen?
ChickenFeet
Übrigens gibt es zwei PKs, so dass der Schlüssel immer noch eindeutig ist.
ChickenFeet
Hallo. Gutes Thema! Aber wie kann ich es standardmäßig ohne "ID" codieren? Ich verwende die Rollenmodellklasse und mache "get_or_create default role" als Standardrollenfeld. Ich sollte ID zurückgeben, aber Objekt zurückgeben
parfeniukink
15

Ich würde die Antwort von @ vault oben leicht ändern (dies könnte eine neue Funktion sein). Es ist auf jeden Fall wünschenswert, das Feld mit einem natürlichen Namen zu bezeichnen. Anstatt das zu überschreiben, Managerwürde ich einfach den to_fieldParameter verwenden ForeignKey:

class Country(models.Model):
    sigla   = models.CharField(max_length=5, unique=True)

    def __unicode__(self):
        return u'%s' % self.sigla

class City(models.Model):
    nome   = models.CharField(max_length=64, unique=True)
    nation = models.ForeignKey(Country, to_field='sigla', default='IT')
Carson McNeil
quelle
12

Ich benutze natürliche Schlüssel, um einen natürlicheren Ansatz zu wählen:

<app>/models.py

from django.db import models

class CountryManager(models.Manager):
    """Enable fixtures using self.sigla instead of `id`"""

    def get_by_natural_key(self, sigla):
        return self.get(sigla=sigla)

class Country(models.Model):
    objects = CountryManager()
    sigla   = models.CharField(max_length=5, unique=True)

    def __unicode__(self):
        return u'%s' % self.sigla

class City(models.Model):
    nome   = models.CharField(max_length=64, unique=True)
    nation = models.ForeignKey(Country, default='IT')
Gewölbe
quelle
2
Ich habe dies in Django 1.6 versucht, aber ich erhalte die Fehlermeldung "Ungültiges Literal für int () mit Basis 10: 'IT'. (Meine Zeichenfolge ist anders, aber Sie haben die Idee.)
Seth
2
Dies funktionierte jedoch gut und scheint eher pythonisch:default=lambda: Country.objects.filter(sigla='IT').first()
Seth
Seltsam, ich erinnere mich, dies mit Django 1.6 getestet zu haben. Googeln Ich fand es ein Tupelproblem, versuche es: def get_by_natural_key(self, sigla): return (self.get(sigla=sigla),)oder auch mit default=('IT',). Ich
Tresor
7

In meinem Fall wollte ich die Standardeinstellung für eine vorhandene Instanz des zugehörigen Modells festlegen . Da es möglich ist, dass die Examwith-ID 1gelöscht wurde, habe ich Folgendes getan:

class Student(models.Model):
    exam_taken = models.ForeignKey("Exam", blank=True)

    def save(self, *args, **kwargs):
        try:
            self.exam_taken
        except:
            self.exam_taken = Exam.objects.first()
        super().save(*args, **kwargs)

Wenn exam_takennicht vorhanden, django.db.models.fields.related_descriptors.RelatedObjectDoesNotExistwird beim Versuch, darauf zuzugreifen, ausgelöst.

Connorbode
quelle
4

Sie könnten dieses Muster verwenden:

class Other(models.Model):
    DEFAULT_PK=1
    name=models.CharField(max_length=1024)

class FooModel(models.Model):
    other=models.ForeignKey(Other, default=Other.DEFAULT_PK)

Natürlich müssen Sie sicher sein, dass die Tabelle von eine Zeile enthält Other. Sie sollten eine Datenmigration verwenden, um sicherzustellen, dass sie vorhanden ist.

guettli
quelle
3

Wie bereits in der Antwort von @ gareth angedeutet , idist es möglicherweise nicht immer die beste Idee, einen Standardwert fest zu codieren :

Wenn der idWert nicht in der Datenbank vorhanden ist, treten Probleme auf. Selbst wenn dieser bestimmte idWert vorhanden ist, kann sich das entsprechende Objekt ändern. In jedem Fall müssten idSie bei Verwendung eines fest codierten Werts auf Datenmigrationen oder die manuelle Bearbeitung vorhandener Datenbankinhalte zurückgreifen.

Um dies zu verhindern, können Sie get_or_create () in Kombination mit einem uniqueanderen Feld als verwenden id.

So würde ich es machen:

from django.db import models

class Exam(models.Model):
    title = models.CharField(max_length=255, unique=True)
    description = models.CharField(max_length=255)

    @classmethod
    def get_default_pk(cls):
        exam, created = cls.objects.get_or_create(
            title='default exam', defaults=dict(description='this is not an exam'))
        return exam.pk


class Student(models.Model):
    exam_taken = models.ForeignKey(to=Exam, on_delete=models.CASCADE,
                                   default=Exam.get_default_pk)

Hier wird ein Exam.titleFeld verwendet, um ein eindeutiges Objekt abzurufen, und ein Exam.descriptionFeld zeigt, wie wir das defaultsArgument (for get_or_create) verwenden können, um das Standardobjekt vollständig anzugeben Exam.

Beachten Sie, dass wir a zurückgeben pk, wie in den Dokumenten vorgeschlagen :

Für Felder wie ForeignKeydieses, die Modellinstanzen zugeordnet sind, sollten Standardwerte der Wert des Felds sein, auf das sie verweisen ( pksofern nicht to_fieldfestgelegt), anstatt Modellinstanzen.

Beachten Sie auch, dass defaultCallables in Model.__init__()( Quelle ) ausgewertet werden . Wenn Ihr Standardwert also von einem anderen Feld desselben Modells oder vom Anforderungskontext oder vom Status des clientseitigen Formulars abhängt, sollten Sie wahrscheinlich woanders suchen .

djvg
quelle
Es funktioniert nicht, wenn Sie noch keine Migrationen für mich haben. Ich verwende die Feldrolle für Benutzer, wobei ich get_default_user_rolewie in Ihrem Beispiel den Rückruf als Standardparameter verwende. Also , wenn ich keine Migrationen habe ich bekam no such table: users_roleauf python manage.py makemigration. Aber wenn ich role fieldim Benutzermodell kommentiere und run makemigrations- ist das kein Problem. Nach dem, was ich auskommentiere role fieldund run migrationswieder ohne Probleme. Vielleicht kannst du mich damit aufhalten?
parfeniukink
@parfeniukink: Ich würde gerne helfen, aber es ist ein bisschen schwierig zu sagen, was falsch läuft, ohne Ihren tatsächlichen Code zu sehen. Vielleicht könnten Sie eine neue Frage mit einigen Details erstellen?
DJVG
Oh, vielen Dank, aber ich habe diese Aufgabe beendet)))
parfeniukink
0

Ich suche nach der Lösung in Django Admin , dann habe ich Folgendes gefunden:

class YourAdmin(admin.ModelAdmin)

    def get_changeform_initial_data(self, request):
        return {'owner': request.user}

Dadurch kann ich auch den aktuellen Benutzer verwenden.

Siehe Django-Dokumente

CK
quelle
0

Der beste Weg, den ich kenne, ist die Verwendung von Lambdas

class TblSearchCase(models.Model):
    weights = models.ForeignKey('TblSearchWeights', models.DO_NOTHING, default=lambda: TblSearchWeights.objects.get(weight_name='value_you_want'))

So können Sie die Standardzeile angeben.

default=lambda: TblSearchWeights.objects.get(weight_name='value_you_want')
DeyaEldeen
quelle