5

I have a simple app (about QR codes) in which I have two models. The first one is for defining the QR Code and the second one is for giving it a function. (For those wondering: I split it up into two models because our QR codes are complex and sometimes lack functions and are read-only. I wanted to keep our database as normalized as possible).

Here is the model (models.py):

from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.utils.translation import ugettext_lazy as _

from core.behaviors import QRCodeable, UniversallyUniqueIdentifiable
from core.utils import QR_CODE_FUNCTIONS
from model_utils.fields import StatusField
from model_utils.models import SoftDeletableModel, TimeStampedModel

QR_CODE_PREFIX = "QR Code"
QR_CODE_FUNCTION_PREFIX = "Function"
QR_CODE_FUNCTION_MIDFIX = "for"


class QRCode(
    UniversallyUniqueIdentifiable,
    SoftDeletableModel,
    TimeStampedModel,
    models.Model
):
    @property
    def function(self):
        try:
            return self.qrcodefunction.qr_code_function
        except ObjectDoesNotExist:
            return ""

    class Meta:
        verbose_name = _('QR code')
        verbose_name_plural = _('QR codes')

    def __str__(self):
        return f'{QR_CODE_PREFIX} {self.uuid}'


class QRCodeFunction(
    UniversallyUniqueIdentifiable,
    SoftDeletableModel,
    TimeStampedModel,
    QRCodeable,
    models.Model
):
    QR_CODE_FUNCTIONS = QR_CODE_FUNCTIONS
    qr_code_function = StatusField(choices_name="QR_CODE_FUNCTIONS")

    class Meta:
        verbose_name = _('QR code function')
        verbose_name_plural = _('QR code functions')

    def __str__(self):
        return f'{QR_CODE_FUNCTION_PREFIX} {self.qr_code_function} {QR_CODE_FUNCTION_MIDFIX} {self.qr_code}'

The mixin QRCodeable is an abstract base class which gives the function a OneToOne relation to the QR code. The mixin UniversallyUniqueIdentifiable gives it a uuid.

Anyways, I now want to be able to create QR codes with functions within the Django admin. So I wrote my own admin class (admin.py):

from django.contrib import admin

from .models import QRCode, QRCodeFunction


class QRCodeFunctionInline(admin.TabularInline):
    model = QRCodeFunction
    extra = 0


@admin.register(QRCode)
class QRCodeAdmin(admin.ModelAdmin):
    save_on_top = True
    search_fields = ['qrcodefunction__qr_code_function']
    list_display = (
        '__str__',
        'function',
    )
    inlines = [
        QRCodeFunctionInline,
    ]

This code results in the following admin interface:

Admin interface of the QR code in the Django admin.

If I now click on add another QR code function, choose a function and hit save, the new instance of QR code function does NOT get created! Why is that? How can I write this model admin so that I can create functions for the QR code in the QR codes admin?

halfer
  • 19,824
  • 17
  • 99
  • 186
J. Hesters
  • 13,117
  • 31
  • 133
  • 249
  • Are you editing that Qr code or adding? can you test this example? https://simpleisbetterthancomplex.com/tutorial/2016/11/23/how-to-add-user-profile-to-django-admin.html The example there use stackedinline and i didn't see 'add another' because is one to one. – Diego Puente Jun 14 '18 at 15:53
  • Is this your complete `admin.py` or did you omit anything? – Daniel Hepper Jun 15 '18 at 09:38
  • `OneToOneField` does no accept null value. Did you take care of that? – Sylvain Biehler Jun 15 '18 at 14:37
  • @daniel-hepper this is the complete admin.py as it stands right now. – J. Hesters Jun 16 '18 at 07:14
  • @sylvain-biehler I'm unsure what you mean. The OneToOneField mustn't be blank and mustn't be null in my definition. – J. Hesters Jun 16 '18 at 07:15
  • @J.Hesters And how is created the related `QR Code`? Can you add the code from `QRCodeable` to the question? – Sylvain Biehler Jun 17 '18 at 12:31
  • @J.Hesters FWIW, I could not reproduce your issue with a minimal example, see https://gist.github.com/dhepper/a656de16297dee6fe53e90fae58c0bf7 please show us more code – Daniel Hepper Jun 18 '18 at 08:20

1 Answers1

0

This is likely a duplicate of Django Admin not saving pre-populated inline fields which are left in their initial state. Your inline will only use the defaults, but Django's admin by default won't actually create an instance if one or more of the fields have been changed. It's a painful experience, but Django errs on the side of caution here. Better to not create than to create and have to delete.

The answer in that question adjusted for your context would be:

from django.contrib import admin
from django.forms.models import ModelForm

class AlwaysChangedModelForm(ModelForm):
    def has_changed(self):
        """ Should returns True if data differs from initial. 
        By always returning true even unchanged inlines will get validated and saved."""
        return True


class QRCodeFunctionInline(admin.TabularInline):
    model = QRCodeFunction
    form = AlwaysChangedModelForm
    extra = 0
schillingt
  • 13,493
  • 2
  • 32
  • 34