Limit a single record in model for django app?

Question:

I want use a model to save the system setting for a django app, So I want to limit the model can only have one record, how to do the limit?

Asked By: zhongshu

||

Answers:

An easy way is to use the setting’s name as the primary key in the settings table. There can’t be more than one record with the same primary key, so that will allow both Django and the database to guarantee integrity.

Answered By: John Feminella

A model with a single allowed row is nothing more than a perverted form of a “persisted object” — maybe even a “persisted singleton”? Don’t do that, that’s not how models work.

Check out https://github.com/danielroseman/django-dbsettings

Answered By: eternicode

Try this:

class MyModel(models.Model):
    onefield = models.CharField('The field', max_length=100)

class MyModelAdmin(admin.ModelAdmin):
  def has_add_permission(self, request):
    # if there's already an entry, do not allow adding
    count = MyModel.objects.all().count()
    if count == 0:
      return True

    return False
Answered By: mpeirwe

William is right, but I guess this is the best practice

def has_add_permission(self, *args, **kwargs):
    return not MyModel.objects.exists()

As reported in the official Django Documentation:

Note: Don’t use this if all you want to do is determine if at least one result exists.
It’s more efficient to use exists().

https://docs.djangoproject.com/en/dev/ref/models/querysets/#when-querysets-are-evaluated

Answered By: Arch

Overwriting has_add_permission works, but in the given examples it breaks the permissions system in Django(staff without necessary permissions can add settings). Here’s a one that doesn’t break it:

class SettingAdmin(admin.ModelAdmin):
    def has_add_permission(self, request):
        base_add_permission = super(SettingAdmin, self).has_add_permission(request)
        if base_add_permission:
            # if there's already an entry, do not allow adding
            count = Setting.objects.all().count()
            if count == 0:
                return True
        return False
Answered By: Umur Kontacı

You can rewrite the save method on your model. Whenever the user wants to register a new record, you can check the existence then, either save the record or, reject the request.

class MyModel(models.Model):
    title = models.CharField(...)
    body = models.TextField(...)
    
    def save(self, *args, **kwargs):
        if MyModel.objects.exists():
            raise ValueError("This model has already its record.")
        else:
            super().save(*args, **kwargs)

You can also use validators. I prefer to use this method.
Hope you find it useful.

Answered By: Sadra

It looks like Ali Reza’s answer but you can update the existed records and return the error message to any form that uses this model. I believe it is reliable and much easy to control.

class MyModel(models.Model):
    ...

    def clean(self):
        super().clean()
        if not self.id and MyModel.objects.exists():
            raise ValidationError('You cannot add more somethings.')
Answered By: aysum

The following is a class I have created which can be used as a singleton class.

from django.db import models
class SingletonModel(models.Model):
    class Meta:
        abstract = True

    def save(self, *args, **kwargs):
        self.__class__.objects.exclude(id=self.id).delete()
        super(SingletonModel, self).save(*args, **kwargs)

    @classmethod
    def load(cls):
        try:
            return cls.objects.get()
        except cls.DoesNotExist:
            return cls()

From the above SingletonModel we can create multiple models, all of which will be having only one record

class ProjectSettings(SingletonModel):
    max_tickets = models.IntegerField(default=15)
    min_tickets = models.IntegerField(default=2)
    ...

We can access the only object of the settings model as follows

ProjectSettings.load().max_tickets

It is also possible to register ProjectSettings to django admin

@admin.register(ProjectSettings)
class ProjectSettingsAdmin(admin.ModelAdmin):
    list_display = [field.name for field in ProjectSettings._meta.get_fields()]
    def has_delete_permission(self, request, obj=None):
        # Nobody is allowed to delete
        return False
Answered By: Akhil RS