Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the standard way of saving something only if its foreign key exists?

I'm using Python 3.7 and Django . I have the following model, with a foreign key to another model ...

class ArticleStat(models.Model):
    objects = ArticleStatManager()
    article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='articlestats')
    ...

    def save(self, *args, **kwargs):
        if self.article.exists():
            try:
                article_stat = ArticleStat.objects.get(article=self.article, elapsed_time_in_seconds=self.elapsed_time_in_seconds)
                self.id = article_stat.id
                super().save(*args, **kwargs, update_fields=["hits"])
            except ObjectDoesNotExist:
                super().save(*args, **kwargs)

I only want to save this if the related foreign key exists, otherwise, I've noticed errors result. What's the standard Django/Python way of doing something like this? I thought I read I could use ".exists()" (Check if an object exists), but instead I get an error

AttributeError: 'Article' object has no attribute 'exists'

Edit: This is the unit test I have to check this ...

    id = 1
    article = Article.objects.get(pk=id)
    self.assertTrue(article, "A pre-condition of this test is that an article exist with id=" + str(id))
    articlestat = ArticleStat(article=article, elapsed_time_in_seconds=250, hits=25)
    # Delete the article
    article.delete()
    # Attempt to save ArticleStat
    articlestat.save()
like image 812
Dave Avatar asked Feb 14 '19 01:02

Dave


People also ask

What is the foreign key constraint?

The FOREIGN KEY constraint is used to prevent actions that would destroy links between tables. A FOREIGN KEY is a field (or collection of fields) in one table, that refers to the PRIMARY KEY in another table.

Is foreign key always a primary key?

A FOREIGN KEY constraint does not have to be linked only to a PRIMARY KEY constraint in another table; it can also be defined to reference the columns of a UNIQUE constraint in another table. So in your case if you make AnotherID unique, it will be allowed.

How do you decide where to put foreign key?

When you join the two tables together, the primary key of the parent table will be set equal to the foreign key of the child table. Whichever one is not the primary key is the foreign key. In one-to-many relationships, the FK goes on the "many" side.

When should you add a foreign key?

Foreign key columns are frequently used in join criteria when the data from related tables is combined in queries by matching the column or columns in the foreign key constraint of one table with the primary or unique key column or columns in the other table.


4 Answers

If you want to be sure Article exists in ArticleStat's save method you can try to get it from your database and not just test self.article.

Quoting Alex Martelli:

" ... Grace Murray Hopper's famous motto, "It's easier to ask forgiveness than permission", has many useful applications -- in Python, ... "

I think using try .. except .. else is more pythonic and I will do something like that:

from django.db import models

class ArticleStat(models.Model):
    ...
    article = models.ForeignKey(
        Article, on_delete=models.CASCADE, related_name='articlestats'
    )

    def save(self, *args, **kwargs):
        try:
            article = Article.objects.get(pk=self.article_id)
        except Article.DoesNotExist:
            pass
        else:
            try:
                article_stat = ArticleStat.objects.get(
                    article=article,
                    elapsed_time_in_seconds=self.elapsed_time_in_seconds
                )
                self.id = article_stat.id
                super().save(*args, **kwargs, update_fields=["hits"])
            except ArticleStat.DoesNotExist:
                super().save(*args, **kwargs)
like image 142
Paolo Melchiorre Avatar answered Oct 22 '22 08:10

Paolo Melchiorre


article field on your ArticleStat model is not optional. You can't save your ArticleStat object without the ForeignKey to Article

Here is a similar code, item is a ForeignKey to the Item model, and it is required.

class Interaction(TimeStampedModel, models.Model):
    ...
    item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='interactions')
    type = models.IntegerField('Type', choices=TYPE_CHOICES)
    ...

If I try to save an object of Interaction from the shell without selecting a ForeignKey to the item, I receive an IntegrityError.

~ interaction = Interaction()
~ interaction.save()
~ IntegrityError: null value in column "item_id" violates not-null constraint

You don't need a check self.article.exists(). Django and Database will require that field and will not let you save the object without it.

You should read about ForeignKey field in Django Docs

like image 28
Yuri Kots Avatar answered Oct 22 '22 08:10

Yuri Kots


If you are using a relational database, foreign key constraints will be added automatically post-migration. save method may not need any customization.

class ArticleStat(models.Model):
    objects = ArticleStatManager()
    article = models.ForeignKey(
        Article, on_delete=models.CASCADE, related_name='articlestats'
    )

Use the following code to create ArticleStats

from django.db import IntegrityError
try:
  ArticleStats.objects.create(article=article, ...)
except IntegrityError:
  pass

If article_id is valid, ArticleStats objects get created else IntegrityError is raised.

article = Article.objects.get(id=1)
article.delete()
try:
  ArticleStats.objects.create(article=article, ...)
  print("article stats is created")
except IntegrityError:
  print("article stats is not created")


# Output
article stats is not created

Note: Tested on MySQL v5.7, Django 1.11

like image 5
sun_jara Avatar answered Oct 22 '22 10:10

sun_jara


You can just test the value of the article field. If it's not set, I believe it defaults to None.

if self.article:  # Value is set

If you want this ForeignKey field to be optional (which it sounds like you do), you need to set blank=True and null=True on that field. This will allow the field to be blank (in validation) and will set null on the field when it's not there.

As mentioned in the comments below, your database is likely enforcing the fact that the field is required, and refuses to remove the article instance.

like image 4
Jonah Bishop Avatar answered Oct 22 '22 08:10

Jonah Bishop