【问题标题】:Django Model Override Only Works if I Save TwiceDjango 模型覆盖仅在我保存两次时才有效
【发布时间】:2020-05-16 20:45:47
【问题描述】:

我在我的模型中实现了保存覆盖,以便使用来自其他模型的输入来计算字段。

在 Admin 中,我有一个 TabularInline 表单。

问题是代码只有在我保存表单两次时才有效。

这是我的 models.py

class School(models.Model):
    name = models.CharField('school name',max_length=200)
    county = models.ForeignKey(County, null=True, on_delete=models.SET_NULL)
    location = models.DecimalField(blank=True, null=True, max_digits=9, decimal_places=6)
    score = models.DecimalField(blank=True, null=True, max_digits=5, decimal_places=2)

    def __str__(self):
        return'{}'.format(self.name)

    def calc_score(self):
        calculated_score = 0
        for item in Marks.objects.filter(school=self.pk):
            for weight in Weights.objects.filter(year=item.year):
                new_score = item.marks*weight.weight
                calculated_score = calculated_score + new_score
        return calculated_score

    def save(self, *args, **kwargs):
        self.score=self.calc_score()
        super(School, self).save(*args, **kwargs)

还有我的 admin.py

class MarksInline (admin.TabularInline):
    model = Marks

@admin.register(School)
class SchoolAdmin(admin.ModelAdmin):
    inlines = [
        MarksInline,
    ]

【问题讨论】:

  • 你怎么知道它只有在你保存模型两次的情况下才有效?您能否更具体地了解您的应用的行为
  • 如果我编辑 formlet 并保存一次,'''score''' 不会更新。但是,如果我再次保存或编辑 formlet,它就会更新。
  • 我猜这与模型的保存顺序有关。当 School 模型保存方法被触发时,Marks 模型可能不会更新。您能否在两次保存事件中检查具有不同标记的计算并分享得分值和您的预期?

标签: python django


【解决方案1】:

问题是SchoolAdminsave_related()之前调用save_model(),所以在计算分数时你的分数没有被保存。我通过覆盖save_related() 并再次调用School.save() 解决了这个问题。

我不得不猜测您的相关表格,但这里有一个完整的、可运行的代码示例。我添加了save_related() 方法。

#!/usr/bin/env python
# -*- coding:utf-8 -*-

""" A Django web app in a single file.

To get it running, copy it into a directory named udjango:
$ pip install django
$ python udjango_web.py

Then browse to http://localhost:8000 and log in to the web site.

Tested with Django 3.0 and Python 3.8.
"""


import os
import sys

import django
from django.conf import settings
from django.conf.urls import url, include
from django.contrib.auth import get_user_model
from django.contrib import admin
from django.core.management import call_command
from django.core.wsgi import get_wsgi_application
from django.db import models
from django.db.models.base import ModelBase
from django.http import HttpResponse

WIPE_DATABASE = True
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DB_FILE = os.path.join(BASE_DIR, 'udjango.db')

# the current folder name will also be our app
APP_LABEL = os.path.basename(BASE_DIR)
urlpatterns = []


def main():
    setup()

    # Create your models here.
    class County(models.Model):
        name = models.CharField(max_length=200)

        def __str__(self):
            return self.name

    class Weights(models.Model):
        year = models.IntegerField()
        weight = models.DecimalField(max_digits=5, decimal_places=2)

        def __str__(self):
            return str(self.year)

    class School(models.Model):
        name = models.CharField('school name', max_length=200)
        county = models.ForeignKey(County, null=True, on_delete=models.SET_NULL)
        location = models.DecimalField(blank=True, null=True, max_digits=9, decimal_places=6)
        score = models.DecimalField(blank=True, null=True, max_digits=5, decimal_places=2)

        def __str__(self):
            return '{}'.format(self.name)

        def calc_score(self):
            calculated_score = 0
            for item in Marks.objects.filter(school=self.pk):
                for weight in Weights.objects.filter(year=item.year):
                    new_score = item.marks * weight.weight
                    calculated_score = calculated_score + new_score
            return calculated_score

        def save(self, *args, **kwargs):
            self.score = self.calc_score()
            super(School, self).save(*args, **kwargs)

    class Marks(models.Model):
        school = models.ForeignKey(School, on_delete=models.CASCADE)
        year = models.IntegerField()
        marks = models.DecimalField(max_digits=5, decimal_places=2)

    # Form classes
    class MarksInline(admin.TabularInline):
        model = Marks

    @admin.register(School)
    class SchoolAdmin(admin.ModelAdmin):
        inlines = [
            MarksInline,
        ]

        def save_related(self, request, form, formsets, change):
            super().save_related(request, form, formsets, change)
            form.instance.save()

    admin.site.register(County)
    admin.site.register(Weights)
    admin.site.register(Marks)
    admin.autodiscover()

    def index(request):
        return HttpResponse(
            "Hello, Django! <a href='admin'>Sign in</a> "
            "as user 'admin', password 'admin'.")

    urlpatterns.extend([
        url(r'^admin/', admin.site.urls),
        url(r'^$', index, name='homepage')
    ])

    if __name__ == "__main__":
        if WIPE_DATABASE or not os.path.exists(DB_FILE):
            with open(DB_FILE, 'w'):
                pass
            call_command('makemigrations', APP_LABEL)
            call_command('migrate')
            get_user_model().objects.create_superuser('admin', '', 'admin')
        call_command('runserver')
    else:
        get_wsgi_application()


def setup():
    sys.path[0] = os.path.dirname(BASE_DIR)

    settings.configure(
        DEBUG=True,
        ROOT_URLCONF=__name__,
        MIDDLEWARE=[
            'django.middleware.security.SecurityMiddleware',
            'django.contrib.sessions.middleware.SessionMiddleware',
            'django.middleware.common.CommonMiddleware',
            'django.middleware.csrf.CsrfViewMiddleware',
            'django.contrib.auth.middleware.AuthenticationMiddleware',
            'django.contrib.messages.middleware.MessageMiddleware',
            'django.middleware.clickjacking.XFrameOptionsMiddleware',
            'django.middleware.locale.LocaleMiddleware',
            ],
        INSTALLED_APPS=[
            APP_LABEL,
            'django.contrib.admin',
            'django.contrib.auth',
            'django.contrib.contenttypes',
            'django.contrib.sessions',
            'django.contrib.messages',
            'django.contrib.staticfiles',
            'rest_framework',
            ],
        STATIC_URL='/static/',
        STATICFILES_DIRS=[
            os.path.join(BASE_DIR, "static"),
        ],
        STATIC_ROOT=os.path.join(BASE_DIR, "static_root"),
        MEDIA_ROOT=os.path.join(BASE_DIR, "media"),
        MEDIA_URL='/media/',
        TEMPLATES=[
            {
                'BACKEND': 'django.template.backends.django.DjangoTemplates',
                'DIRS': [os.path.join(BASE_DIR, "templates")],
                'APP_DIRS': True,
                'OPTIONS': {
                    'context_processors': [
                        'django.template.context_processors.debug',
                        'django.template.context_processors.i18n',
                        'django.template.context_processors.request',
                        'django.contrib.auth.context_processors.auth',
                        'django.template.context_processors.tz',
                        'django.contrib.messages.context_processors.messages',
                    ],
                },
            },
            ],
        DATABASES={
            'default': {
                'ENGINE': 'django.db.backends.sqlite3',
                'NAME': DB_FILE,
                }
            },
        REST_FRAMEWORK={
            'DEFAULT_PERMISSION_CLASSES': [
                'rest_framework.permissions.IsAdminUser',
            ],
        }
    )

    django.setup()
    original_new_func = ModelBase.__new__

    @staticmethod
    def patched_new(cls, name, bases, attrs):
        if 'Meta' not in attrs:
            class Meta:
                app_label = APP_LABEL
            attrs['Meta'] = Meta
        return original_new_func(cls, name, bases, attrs)
    ModelBase.__new__ = patched_new


main()

【讨论】:

  • 像魅力一样工作!
猜你喜欢
  • 2018-10-07
  • 1970-01-01
  • 2019-10-06
  • 1970-01-01
  • 2012-11-06
  • 2016-05-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多