django-models انواع - كيفية العمل حول عدم وجود دعم للمفاتيح الأجنبية عبر قواعد البيانات في Django




المفاتيح المفتاح (9)

وأنا أعلم أن Djano-nosql لديه دعم للمفاتيح وعلى الرغم من بعض السحر من http://www.allbuttonspressed.com/projects/django-dbindexer . ربما بعض من هذا يمكن أن يساعد.

من الوصف:

"يمكنك فقط إخبار dbindexer عن النماذج والحقول التي يجب أن تدعم هذه الاستعلامات وأنها ستهتم بالحفاظ على الفهارس المطلوبة لك."

-Kerry

أعلم أن Django لا يدعم المفاتيح الأجنبية عبر قواعد بيانات متعددة (في الأصل dangang 1.3 docs)

لكنني أبحث عن حل.

ما لا يعمل

لدي كل نموذجين على قاعدة بيانات منفصلة.

routers.py:

class NewsRouter(object):
    def db_for_read(self, model, **hints):
        if model._meta.app_label == 'news_app':
            return 'news_db'
        return None

    def db_for_write(self, model, **hints):
        if model._meta.app_label == 'news_app':
            return 'news_db'
        return None

    def allow_relation(self, obj1, obj2, **hints):
        if obj1._meta.app_label == 'news_app' or obj2._meta.app_label == 'news_app':
            return True
        return None

    def allow_syncdb(self, db, model):
        if db == 'news_db':
            return model._meta.app_label == 'news_app'
        elif model._meta.app_label == 'news_app':
            return False
        return None

النموذج 1 في fruit_app / models.py:

from django.db import models

class Fruit(models.Model):
    name = models.CharField(max_length=20)

النموذج 2 في news_app / models.py:

from django.db import models

class Article(models.Model):
    fruit = models.ForeignKey('fruit_app.Fruit')
    intro = models.TextField()

محاولة إضافة "مقالة" في المسؤول يعطي الخطأ التالي لأنه يبحث عن طراز Fruit على قاعدة البيانات خاطئ ( 'news_db' ):

DatabaseError at /admin/news_app/article/add/

(1146, "Table 'fkad_news.fruit_app_fruit' doesn't exist")

الطريقة الأولى: فئة فرعية IntegerField

قمت بإنشاء حقل مخصص ، ForeignKeyAcrossDb ، وهو فئة فرعية من IntegerField. رمز على جيثب في: https://github.com/saltycrane/django-foreign-key-across-db-testproject/tree/integerfield_subclass

fields.py:

from django.db import models


class ForeignKeyAcrossDb(models.IntegerField):
    '''
    Exists because foreign keys do not work across databases
    '''
    def __init__(self, model_on_other_db, **kwargs):
        self.model_on_other_db = model_on_other_db
        super(ForeignKeyAcrossDb, self).__init__(**kwargs)

    def to_python(self, value):
        # TODO: this db lookup is duplicated in get_prep_lookup()
        if isinstance(value, self.model_on_other_db):
            return value
        else:
            return self.model_on_other_db._default_manager.get(pk=value)

    def get_prep_value(self, value):
        if isinstance(value, self.model_on_other_db):
            value = value.pk
        return super(ForeignKeyAcrossDb, self).get_prep_value(value)

    def get_prep_lookup(self, lookup_type, value):
        # TODO: this db lookup is duplicated in to_python()
        if not isinstance(value, self.model_on_other_db):
            value = self.model_on_other_db._default_manager.get(pk=value)

        return super(ForeignKeyAcrossDb, self).get_prep_lookup(lookup_type, value)

لقد غيرت نموذج مقالتي ليكون:

class Article(models.Model):
    fruit = ForeignKeyAcrossDb(Fruit)
    intro = models.TextField()

المشكلة هي ، في بعض الأحيان عندما أتمكن من الوصول إلى Article.fruit ، وهو عدد صحيح ، وأحيانا هو كائن الفاكهة. أريد أن يكون دائما كائن الفاكهة. ما الذي أحتاج إلى القيام به لجعل الوصول إلى Article.fruit دائمًا إرجاع كائن Fruit؟

كحل بديل لحل المشكلة ، أضفت خاصية fruit_obj ، لكنني أرغب في إزالة هذا الأمر إن أمكن:

class Article(models.Model):
    fruit = ForeignKeyAcrossDb(Fruit)
    intro = models.TextField()

    # TODO: shouldn't need fruit_obj if ForeignKeyAcrossDb field worked properly
    @property
    def fruit_obj(self):
        if not hasattr(self, '_fruit_obj'):
            # TODO: why is it sometimes an int and sometimes a Fruit object?
            if isinstance(self.fruit, int) or isinstance(self.fruit, long):
                print 'self.fruit IS a number'
                self._fruit_obj = Fruit.objects.get(pk=self.fruit)
            else:
                print 'self.fruit IS NOT a number'
                self._fruit_obj = self.fruit
        return self._fruit_obj

    def fruit_name(self):
        return self.fruit_obj.name

الطريقة 2: حقل ForeignKey فئة فرعية

كمحاولة ثانية ، جربت subclassing حقل ForeignKey. قمت بتعديل ReverseSingleRelatedObjectDescriptor لاستخدام قاعدة البيانات المحددة بواسطة forced_using على مدير طراز Fruit . قمت أيضاً بإزالة أسلوب validate() على فئة فرعية ForeignKey . لم يكن هذا الأسلوب نفس المشكلة كما الطريقة 1. التعليمات البرمجية على جيثب في: https://github.com/saltycrane/django-foreign-key-across-db-testproject/tree/foreignkey_subclass

fields.py:

from django.db import models
from django.db import router
from django.db.models.query import QuerySet


class ReverseSingleRelatedObjectDescriptor(object):
    # This class provides the functionality that makes the related-object
    # managers available as attributes on a model class, for fields that have
    # a single "remote" value, on the class that defines the related field.
    # In the example "choice.poll", the poll attribute is a
    # ReverseSingleRelatedObjectDescriptor instance.
    def __init__(self, field_with_rel):
        self.field = field_with_rel

    def __get__(self, instance, instance_type=None):
        if instance is None:
            return self

        cache_name = self.field.get_cache_name()
        try:
            return getattr(instance, cache_name)
        except AttributeError:
            val = getattr(instance, self.field.attname)
            if val is None:
                # If NULL is an allowed value, return it.
                if self.field.null:
                    return None
                raise self.field.rel.to.DoesNotExist
            other_field = self.field.rel.get_related_field()
            if other_field.rel:
                params = {'%s__pk' % self.field.rel.field_name: val}
            else:
                params = {'%s__exact' % self.field.rel.field_name: val}

            # If the related manager indicates that it should be used for
            # related fields, respect that.
            rel_mgr = self.field.rel.to._default_manager
            db = router.db_for_read(self.field.rel.to, instance=instance)
            if getattr(rel_mgr, 'forced_using', False):
                db = rel_mgr.forced_using
                rel_obj = rel_mgr.using(db).get(**params)
            elif getattr(rel_mgr, 'use_for_related_fields', False):
                rel_obj = rel_mgr.using(db).get(**params)
            else:
                rel_obj = QuerySet(self.field.rel.to).using(db).get(**params)
            setattr(instance, cache_name, rel_obj)
            return rel_obj

    def __set__(self, instance, value):
        raise NotImplementedError()

class ForeignKeyAcrossDb(models.ForeignKey):

    def contribute_to_class(self, cls, name):
        models.ForeignKey.contribute_to_class(self, cls, name)
        setattr(cls, self.name, ReverseSingleRelatedObjectDescriptor(self))
        if isinstance(self.rel.to, basestring):
            target = self.rel.to
        else:
            target = self.rel.to._meta.db_table
        cls._meta.duplicate_targets[self.column] = (target, "o2m")

    def validate(self, value, model_instance):
        pass

fruit_app / models.py:

from django.db import models


class FruitManager(models.Manager):
    forced_using = 'default'


class Fruit(models.Model):
    name = models.CharField(max_length=20)

    objects = FruitManager()

news_app / models.py:

from django.db import models

from foreign_key_across_db_testproject.fields import ForeignKeyAcrossDb
from foreign_key_across_db_testproject.fruit_app.models import Fruit


class Article(models.Model):
    fruit = ForeignKeyAcrossDb(Fruit)
    intro = models.TextField()

    def fruit_name(self):
        return self.fruit.name

الطريقة 2 أ: إضافة جهاز توجيه لـ fruit_app

يستخدم هذا الحل جهاز توجيه إضافي لـ fruit_app . لا يتطلب هذا الحل التعديلات على ForeignKey التي كانت مطلوبة في الطريقة الثانية. بعد النظر إلى سلوك التوجيه الافتراضي في Django في django.db.utils.ConnectionRouter ، وجدنا أنه على الرغم من أننا توقعنا أن يكون fruit_app على قاعدة البيانات 'default' بشكل افتراضي ، تمرير تلميح instance إلى db_for_read لعمليات البحث عن المفتاح الخارجي وضعها في قاعدة البيانات 'news_db' . أضفنا جهاز توجيه ثان لضمان fruit_app نماذج fruit_app دائمًا من قاعدة البيانات 'default' . يتم استخدام فئة فرعية ForeignKey فقط "لإصلاح" الأسلوب ForeignKey.validate() . (إذا كان جانغو يريد دعم المفاتيح الأجنبية عبر قواعد البيانات ، أود أن أقول أن هذا هو خطأ جانغو.) رمز على جيثب في: https://github.com/saltycrane/django-foreign-key-across-db-testproject

routers.py:

class NewsRouter(object):
    def db_for_read(self, model, **hints):
        if model._meta.app_label == 'news_app':
            return 'news_db'
        return None

    def db_for_write(self, model, **hints):
        if model._meta.app_label == 'news_app':
            return 'news_db'
        return None

    def allow_relation(self, obj1, obj2, **hints):
        if obj1._meta.app_label == 'news_app' or obj2._meta.app_label == 'news_app':
            return True
        return None

    def allow_syncdb(self, db, model):
        if db == 'news_db':
            return model._meta.app_label == 'news_app'
        elif model._meta.app_label == 'news_app':
            return False
        return None


class FruitRouter(object):
    def db_for_read(self, model, **hints):
        if model._meta.app_label == 'fruit_app':
            return 'default'
        return None

    def db_for_write(self, model, **hints):
        if model._meta.app_label == 'fruit_app':
            return 'default'
        return None

    def allow_relation(self, obj1, obj2, **hints):
        if obj1._meta.app_label == 'fruit_app' or obj2._meta.app_label == 'fruit_app':
            return True
        return None

    def allow_syncdb(self, db, model):
        if db == 'default':
            return model._meta.app_label == 'fruit_app'
        elif model._meta.app_label == 'fruit_app':
            return False
        return None

fruit_app / models.py:

from django.db import models


class Fruit(models.Model):
    name = models.CharField(max_length=20)

news_app / models.py:

from django.db import models

from foreign_key_across_db_testproject.fields import ForeignKeyAcrossDb
from foreign_key_across_db_testproject.fruit_app.models import Fruit


class Article(models.Model):
    fruit = ForeignKeyAcrossDb(Fruit)
    intro = models.TextField()

    def fruit_name(self):
        return self.fruit.name

fields.py:

from django.core import exceptions
from django.db import models
from django.db import router


class ForeignKeyAcrossDb(models.ForeignKey):

    def validate(self, value, model_instance):
        if self.rel.parent_link:
            return
        models.Field.validate(self, value, model_instance)
        if value is None:
            return

        using = router.db_for_read(self.rel.to, instance=model_instance)  # is this more correct than Django's 1.2.5 version?
        qs = self.rel.to._default_manager.using(using).filter(
                **{self.rel.field_name: value}
             )
        qs = qs.complex_filter(self.rel.limit_choices_to)
        if not qs.exists():
            raise exceptions.ValidationError(self.error_messages['invalid'] % {
                'model': self.rel.to._meta.verbose_name, 'pk': value})

معلومة اضافية

تحديث

لقد نفذنا الطريقة الأخيرة بعد التغيير بشكل أكبر في أجهزة التوجيه الخاصة بنا. لقد كان التنفيذ كله مؤلما للغاية مما يجعلنا نعتقد أنه يجب علينا أن نفعل ذلك بشكل خاطئ. في قائمة TODO يتم كتابة اختبارات الوحدة لهذا الغرض.


لدي حل جديد ل django v1.10. هناك جزئين. وهو يعمل مع django.admin و django.rest-framework.

  1. توارث فئة ForeignKey وإنشاء ForeignKeyAcrossDb ، وتجاوز الدالة validate() ، استناداً إلى هذه ticket وهذه post .

class ForeignKeyAcrossDb(models.ForeignKey):
        def validate(self, value, model_instance):
            if self.remote_field.parent_link:
                return
            super(models.ForeignKey, self).validate(value, model_instance)
            if value is None:
                return
            using = router.db_for_read(self.remote_field.model, instance=model_instance)
            qs = self.remote_field.model._default_manager.using(using).filter(
                **{self.remote_field.field_name: value}
            )
            qs = qs.complex_filter(self.get_limit_choices_to())
            if not qs.exists():
                raise exceptions.ValidationError(
                    self.error_messages['invalid'],
                    code='invalid',
                    params={
                        'model': self.remote_field.model._meta.verbose_name, 'pk': value,
                        'field': self.remote_field.field_name, 'value': value,
                    },  # 'pk' is included for backwards compatibility
                )
  1. في db_constraint=False الحقل ، استخدم db_constraint=False ، على سبيل المثال ،

album=ForeignKeyAcrossDb(Singer, db_constraint=False, on_delete=models.DO_NOTHING)


بالنسبة إلى جزء ForeignKeyAcrossDb ، ألا يمكنك إجراء بعض التعديلات على صفك داخل __init__ ؟ تحقق مما إذا كان الحقل المناسب هو Integer إن لم يكن ، أو تحميله من قاعدة البيانات ، أو القيام بأي شيء آخر مطلوب. بايثون __class__ es يمكن تغييرها في وقت التشغيل دون مشكلة كبيرة.


تتم كتابة هذا الحل في الأصل لقاعدة بيانات مُدارة واحدة مع عمليات الترحيل وقاعدة بيانات قديمة أو أكثر بنماذج Meta managed=False المتصلة على مستوى قاعدة البيانات إلى نفس قاعدة البيانات. إذا كان خيار db_table يحتوي على اسم قاعدة بيانات بالإضافة إلى اسم الجدول مقتبس بشكل صحيح من قبل '' ' (MySQL) أو بواسطة ' '' (ديسيبل أخرى) ، على سبيل المثال db_table = '"DB2"."table_b"' ، ثم لم يتم اقتباسها بعد الآن جانغو ORM بشكل صحيح ، حتى مع JOINs:

class TableB(models.Model):
    ....
    class Meta:    
        db_table = '`DB2`.`table_b`'    # for MySQL
        # db_table = '"DB2"."table_b"'  # for all other backends
        managed = False

مجموعة الاستعلام:

>>> qs = TableB.objects.all()
>>> str(qs.query)
'SELECT "DB2"."table_b"."id" FROM DB2"."table_b"'

مدعوم من قبل جميع backb ديسيبل في Django.

(يبدو أنني بدأت مكافأة على سؤال جديد مكرر حيث تستمر إجابتي.)


بعد كسر رأسي في بعض الأيام ، تمكنت من الحصول على مفتاحي الخارجي على نفس البنك!

يمكن إجراء تغيير على النموذج للبحث عن مفتاح خارجي في بنك آخر!

أولا ، إضافة RECHARGE من الحقول ، سواء مباشرة (الكراك) شكلي ، في وظيفة _ init _

app.form.py

# -*- coding: utf-8 -*-
from django import forms
import datetime
from app_ti_helpdesk import models as mdp

#classe para formulario de Novo HelpDesk
class FormNewHelpDesk(forms.ModelForm):
    class Meta:
        model = mdp.TblHelpDesk
        fields = (
        "problema_alegado",
        "cod_direcionacao",
        "data_prevista",
        "hora_prevista",
        "atendimento_relacionado_a",
        "status",
        "cod_usuario",
        )

    def __init__(self, *args, **kwargs):
        #-------------------------------------
        #  using remove of kwargs
        #-------------------------------------
        db = kwargs.pop("using", None)

        # CASE use Unique Keys
        self.Meta.model.db = db

        super(FormNewHelpDesk, self).__init__(*args,**kwargs)

        #-------------------------------------
        #   recreates the fields manually
        from copy import deepcopy
        self.fields = deepcopy( forms.fields_for_model( self.Meta.model, self.Meta.fields, using=db ) )
        #
        #-------------------------------------

        #### follows the standard template customization, if necessary

        self.fields['problema_alegado'].widget.attrs['rows'] = 3
        self.fields['problema_alegado'].widget.attrs['cols'] = 22
        self.fields['problema_alegado'].required = True
        self.fields['problema_alegado'].error_messages={'required': 'Necessário informar o motivo da solicitação de ajuda!'}


        self.fields['data_prevista'].widget.attrs['class'] = 'calendario'
        self.fields['data_prevista'].initial = (datetime.timedelta(4)+datetime.datetime.now().date()).strftime("%Y-%m-%d")

        self.fields['hora_prevista'].widget.attrs['class'] = 'hora'
        self.fields['hora_prevista'].initial =datetime.datetime.now().time().strftime("%H:%M")

        self.fields['status'].initial = '0'                 #aberto
        self.fields['status'].widget.attrs['disabled'] = True

        self.fields['atendimento_relacionado_a'].initial = '07'

        self.fields['cod_direcionacao'].required = True
        self.fields['cod_direcionacao'].label = "Direcionado a"
        self.fields['cod_direcionacao'].initial = '2'
        self.fields['cod_direcionacao'].error_messages={'required': 'Necessário informar para quem é direcionado a ajuda!'}

        self.fields['cod_usuario'].widget = forms.HiddenInput()

استدعاء النموذج من العرض

app.view.py

form = forms.FormNewHelpDesk(request.POST or None, using=banco)

الآن ، التغيير في المصدر كود DJANGO

يمكن فقط الحقول من نوع ForeignKey و ManyToManyField و OneToOneField استخدام 'using' ، لذا أضف IF ...

django.forms.models.py

# line - 133: add using=None
def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_callback=None, using=None):

# line - 159

if formfield_callback is None:
    #----------------------------------------------------
    from django.db.models.fields.related import (ForeignKey, ManyToManyField, OneToOneField)
    if type(f) in (ForeignKey, ManyToManyField, OneToOneField):
        kwargs['using'] = using

    formfield = f.formfield(**kwargs)
    #----------------------------------------------------
elif not callable(formfield_callback):
    raise TypeError('formfield_callback must be a function or callable')
else:
    formfield = formfield_callback(f, **kwargs)

ALTER FOLLOW FILE

django.db.models.base.py

تغير

# line 717
qs = model_class._default_manager.filter(**lookup_kwargs)

إلى عن على

# line 717
qs = model_class._default_manager.using(getattr(self, 'db', None)).filter(**lookup_kwargs)

جاهز: د


واجهت مشكلة مماثلة من الحاجة إلى مرجعية (في الغالب) البيانات الثابتة عبر قواعد بيانات متعددة (5). تم إجراء تحديث طفيف على ReversedSingleRelatedObjectDescriptor للسماح بتعيين الطراز ذي الصلة. إنه لا ينفذ العلاقة العكسية.

class ReverseSingleRelatedObjectDescriptor(object):
"""
This class provides the functionality that makes the related-object managers available as attributes on a model
class, for fields that have a single "remote" value, on the class that defines the related field. Used with
LinkedField.
"""
def __init__(self, field_with_rel):
    self.field = field_with_rel
    self.cache_name = self.field.get_cache_name()

def __get__(self, instance, instance_type=None):
    if instance is None:
        return self

    try:
        return getattr(instance, self.cache_name)
    except AttributeError:
        val = getattr(instance, self.field.attname)
        if val is None:
            # If NULL is an allowed value, return it
            if self.field.null:
                return None
            raise self.field.rel.to.DoesNotExist
        other_field = self.field.rel.get_related_field()
        if other_field.rel:
            params = {'%s__pk' % self.field.rel.field_name: val}
        else:
            params = {'%s__exact' % self.field.rel.field_name: val}

        # If the related manager indicates that it should be used for related fields, respect that.
        rel_mgr = self.field.rel.to._default_manager
        db = router.db_for_read(self.field.rel.to, instance=instance)
        if getattr(rel_mgr, 'forced_using', False):
            db = rel_mgr.forced_using
            rel_obj = rel_mgr.using(db).get(**params)
        elif getattr(rel_mgr, 'use_for_related_fields', False):
            rel_obj = rel_mgr.using(db).get(**params)
        else:
            rel_obj = QuerySet(self.field.rel.to).using(db).get(**params)
        setattr(instance, self.cache_name, rel_obj)
        return rel_obj

def __set__(self, instance, value):
    if instance is None:
        raise AttributeError("%s must be accessed via instance" % self.field.name)

    # If null=True, we can assign null here, but otherwise the value needs to be an instance of the related class.
    if value is None and self.field.null is False:
        raise ValueError('Cannot assign None: "%s.%s" does not allow null values.' %
                         (instance._meta.object_name, self.field.names))
    elif value is not None and not isinstance(value, self.field.rel.to):
        raise ValueError('Cannot assign "%r": "%s.%s" must be a "%s" instance.' %
                         (value, instance._meta.object_name, self.field.name, self.field.rel.to._meta.object_name))
    elif value is not None:
        # Only check the instance state db, LinkedField implies that the value is on a different database
        if instance._state.db is None:
            instance._state.db = router.db_for_write(instance.__class__, instance=value)

    # Is not used by OneToOneField, no extra measures to take here

    # Set the value of the related field
    try:
        val = getattr(value, self.field.rel.get_related_field().attname)
    except AttributeError:
        val = None
    setattr(instance, self.field.attname, val)

    # Since we already know what the related object is, seed the related object caches now, too. This avoids another
    # db hit if you get the object you just set
    setattr(instance, self.cache_name, value)
    if value is not None and not self.field.rel.multiple:
        setattr(value, self.field.related.get_cache_name(), instance)

و

class LinkedField(models.ForeignKey):
"""
Field class used to link models across databases. Does not ensure referrential integraty like ForeignKey
"""
def _description(self):
    return "Linked Field (type determined by related field)"

def contribute_to_class(self, cls, name):
    models.ForeignKey.contribute_to_class(self, cls, name)
    setattr(cls, self.name, ReverseSingleRelatedObjectDescriptor(self))
    if isinstance(self.rel.to, basestring):
        target = self.rel.to
    else:
        target = self.rel.to._meta.db_table
    cls._meta.duplicate_targets[self.column] = (target, "o2m")

def validate(self, value, model_instance):
    pass

يشير حقل المفتاح الخارجي إلى أنه يمكنك - الاستعلام عن العلاقة من خلال الانضمام إلى أي نوع من الفواكه - التحقق من سلامة المرجع - التأكد من التكامل المرجعي عند الحذف - وظيفة البحث عن معرف خام أساسي - (المزيد ...)

ستكون حالة الاستخدام الأولى مشكلة دائمًا. ربما هناك بعض الحالات الأجنبية الخاصة الرئيسية الأخرى في أكواد البرمجة والتي لن تعمل.

أدير موقع django كبير بالأحرى ونحن نستخدم حاليا integerfield عادي. في الوقت الحالي ، أعتقد أن وضع فئة فرعية على الرقم الصحيح وإضافة الرقم إلى عملية تحويل الكائن سيكون أسهل (في 1.2 يتطلب تصحيح بعض أجزاء من django ، نأمل أن يتم تحسينها الآن) سيسمح لك بمعرفة ما الحل الذي نجده.


مستوحاة من تعليقFrans. الحل الخاص بي هو القيام بذلك في طبقة الأعمال. في المثال المقدم هذا السؤال. أود أن أضع ثمارًا لـ IntegerField على Article ، كـ "عدم القيام بفحص النزاهة في طبقة البيانات".

class Fruit(models.Model):
    name = models.CharField()

class Article(models.Model):
    fruit = models.IntegerField()
    intro = models.TextField()

ثم اشرح العلاقة المرجعية في كود التطبيق (طبقة الأعمال). لنأخذ على سبيل المثال مدير Django ، لعرض الفاكهة كخيار في صفحة إضافة المقالة ، يمكنك ملء قائمة الاختيارات للفاكهة يدويًا.

# admin.py in App article
class ArticleAdmin(admin.ModelAdmin):
    class ArticleForm(forms.ModelForm):
        fields = ['fruit', 'intro']

        # populate choices for fruit
        choices = [(obj.id, obj.name) for obj in Fruit.objects.all()]
        widgets = {
            'fruit': forms.Select(choices=choices)}

    form = ArticleForm
    list_diaplay = ['fruit', 'intro']

بالطبع قد تحتاج إلى رعاية التحقق من صحة حقل النموذج (التحقق من سلامة).


في الواقع ، لا تساعدك المعاملات كثيراً هنا ... إلا إذا كنت تريد إجراء معاملات عبر طلبات HTTP متعددة (والتي لا تريدها على الأرجح).

ما نستخدمه عادة في تلك الحالات هو "قفل متفائل". لا يدعم جانغو ORM ذلك بقدر ما أعرف. ولكن كان هناك بعض النقاش حول إضافة هذه الميزة.

لذلك أنت وحدك. في الأساس ، ما يجب عليك فعله هو إضافة حقل "version" إلى النموذج الخاص بك وتمريره إلى المستخدم كحقل مخفي. الدورة العادية للتحديث هي:

  1. قراءة البيانات وإظهارها للمستخدم
  2. المستخدم تعديل البيانات
  3. مستخدم آخر البيانات
  4. التطبيق يحفظه مرة أخرى في قاعدة البيانات.

لتنفيذ تأمين متفائل ، عند حفظ البيانات ، تحقق مما إذا كان الإصدار الذي حصلت عليه من المستخدم هو نفسه الموجود في قاعدة البيانات ، ثم قم بتحديث قاعدة البيانات وزيادة الإصدار. إذا لم تكن كذلك ، فهذا يعني أنه حدث تغيير منذ تحميل البيانات.

يمكنك القيام بذلك من خلال مكالمة SQL واحدة تتضمن شيئًا مثل:

UPDATE ... WHERE version = 'version_from_user';

لن تقوم هذه المكالمة بتحديث قاعدة البيانات إلا إذا كان الإصدار لا يزال هو نفسه.







django django-models django-orm