Derek Adair
Derek Adair

Reputation: 21915

Auto Incrementing natural keys with django / postgres

Let me preface this in saying that I'm a UI dev who's trying to branch out into more backend coding, so excuse me if my verbiage is off at all. This is could be a duplicate, but i'm not sure what on god's good green earth i'm even supposed to call what i want to do.


Basically, I have categories, and images. I need to label each image with an acronym of the category it belongs to, and increment a sku after.

For Example, the following images would be automatically labeled like...

ABC-1
ABC-2
DEF-1
DEF-2
DEF-3
ABC-3*

*note: I want it to increment the ID based on the category, not the total # of images

How would I achieve this in idiomatic Django?

Models:

class Group(models.Model):
    title       = models.CharField(max_length=200)
    abbv        = models.CharField(max_length=200)
    urlified    = models.CharField(max_length=200)
    description = models.TextField(blank=True)
    hidden      = models.BooleanField()

    def __unicode__(self):
        return self.title

class Photo(models.Model):
    group       = models.ForeignKey(Group)
    title       = models.CharField(max_length=200)
    description = models.TextField(blank=True)
    pub_date    = models.DateTimeField(auto_now_add = True, blank=True)
    image       = models.ImageField(max_length=100)

    class Meta:
        ordering = ('pub_date',)

Upvotes: 2

Views: 272

Answers (2)

If you want true composed primary keys, you might want to use django-compositepks, but that is not ideal. You might be better off breaking DRY and recording the number (see the category_auto_key field and default).

Transactions will solve it this way:

from django.db import transaction

class Group(models.model):
    # your fields
    img_count = models.IntegerField()

    @transaction.atomic
    def next_sku(self):
        self.img_count += 1
        self.save()
        return self.img_count

class Photo(models.Model):
    # your fields
    category_auto_key = models.IntegerField(editable=False)

    def category_image(self):
        return self.group.abbv+"-"+str(self.category_auto_key)

    def save(self, *args, **kwargs):
        if not self.category_auto_key:
            self.category_auto_key = self.group.next_sku()
        super(Photo, self).save(*args, **kwargs)

When you need this in your templates, just enclose it in double brackets:

{{ photo.category_image }}

Upvotes: 3

macguru2000
macguru2000

Reputation: 2122

I'm curious if you just want to generate and store the acronym and sku in a text field, or if you are trying to create relationships between your image categories?

If the later, I would look for a different approach.

If the former, i would use a customized set or save method (hook?) for your image model. It will need do a small one time lookup to count the number of acronym already existing, but I wouldn't worry about the performance too much.

Wasn't sure how to do this exactly in Django off the top of my head, but it looks like the accepted answer works similarly. Anyways, here is my attempt at setting a Model Field during save. Be warned this in untested.

After looking into it more I think that Beltiras' solution is better

class Photo(models.Model):
    # simple column definitions
    group       = models.ForeignKey(Group)
    title       = models.CharField(max_length=200)
    description = models.TextField(blank=True)
    pub_date    = models.DateTimeField(auto_now_add = True, blank=True)
    image       = models.ImageField(max_length=100)

    # new column for storing abbv sku
    category_label = models.CharField(max_length=200)

    # save override
    def save(self, *args, **kwargs):
        # hopefully only set category_label on first save, not sure this
        # works, open to other ideas
        if (self.pk is None):
            count = Photo.objects.filter(group=self.group).count()
            label = self.group.abbv + '-' + count
            setattr(self, 'category_label', label)

        # call the super class' save method
        super(Photo, self).save(*args, ** kwargs)

The part I am least sure about is:

count = Photo.objects.filter(group=self.group).count()

The idea is to query the photos table for photos in the same group and count them. This may need to be replaced with a direct SQL call or done some other way. Let me know what you find.

Upvotes: 1

Related Questions