Каков стандартный способ сохранения чего-либо, только если существует его внешний ключ? - PullRequest
0 голосов
/ 14 февраля 2019

Я использую Python 3.7 и Django.У меня есть следующая модель с внешним ключом к другой модели ...

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)

Я хочу сохранить это, только если существует связанный внешний ключ, в противном случае, я заметил ошибки.Какой стандартный способ Django / Python сделать что-то подобное?Я думал, что прочитал, что могу использовать «.exists ()» ( Проверить, существует ли объект ), но вместо этого я получаю ошибку

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

Редактировать: Это юнит тест, я должен проверить это ...

    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()

Ответы [ 7 ]

0 голосов
/ 22 февраля 2019

Как указывалось в других ответах, Article ForeignKey требуется для вашей модели ArticleStat, и сохранение автоматически завершится неудачно без действительного экземпляра Article.Лучший способ избежать неудачи из-за неверного ввода - использовать проверку Form с API форм Django .Или при обработке сериализованных данных с помощью Django Rest Framework, используя Serializer, который является аналогом формы для данных JSON.Таким образом, вам не нужно перезаписывать метод save, если у вас нет особых требований.

Пока никто не упомянул правильное использование .exists(). Это метод queryset , а не экземпляр модели , поэтому вы получаете ошибку, которую выупоминалось выше при попытке применить его к отдельному экземпляру модели с помощью self.article.exists().Чтобы проверить существование объекта, просто используйте .filter вместо .get.Если ваша статья (pk=1) существует, то:

Article.objects.filter(pk=1)

вернет набор запросов с одной статьей:

<Queryset: [Article: 1]>

и

Article.objects.filter(pk=1).exists()

Willвозврат True.Принимая во внимание, что если элемент не существует, запрос возвратит пустой набор запросов и .exists() вернет False, а не вызовет исключение (как попытка .get() несуществующего объекта).Это по-прежнему применимо, если pk ранее существовал и был удален.

РЕДАКТИРОВАТЬ: Только что заметил, что ваше поведение ArticleStat on_delete в настоящее время установлено на CASCADE.Это означает, что при удалении статьи соответствующая статья также удаляется.Поэтому я думаю, что вы, вероятно, неправильно истолковали ошибки / трудности, о которых вы упоминали в ответ на ответ @ jonah-bishop при попытке if self.article:.Для минимального исправления, если вы все еще хотите сохранить ArticleStat после удаления статьи, измените ключевое слово on_delete на models.SET_NULL и, согласно ответу Джоны, добавьте дополнительные ключевые слова null=True, blank=True:

article = models.ForeignKey(Article, on_delete=models.SET_NULL, related_name='articlestats', null=True, blank=True)

Тогда не должно возникнуть никаких проблем, если просто выполнить if self.article: для проверки правильности отношения объекта ForeignKey.Однако использование форм / сериализаторов все же лучше.

0 голосов
/ 23 февраля 2019

Вы можете позвонить .full_clean() до .save()

from django.core.exceptions import ValidationError

class ArticleStat(models.Model):
    #...
    def save(self, *args, **kwargs):
        try:
            self.full_clean()
        except ValidationError as e:
            # dont save
            # Do something based on the errors contained in e.message_dict.
            # Display them to a user, or handle them programmatically.
            pass
        else:
            super().save(*args, **kwargs)
0 голосов
/ 21 февраля 2019

Код последней строки articlestat.save() завершится ошибкой, если экземпляр article был удален.Django и база данных автоматически проверит статью, если вы используете базу данных отношений, такую ​​как mysql или sqlite3.

Во время миграции будет создано ограничение.Например:

shell>>> python manage.py sqlmigrate <appname> 0001
CREATE TABLE impress_impress ...
...

ALTER TABLE `impress_impress` ADD CONSTRAINT 
    `impress_impress_target_id_73acd523_fk_account_myuser_id` FOREIGN KEY (`target_id`) 
    REFERENCES `account_myuser` (`id`);

...

Поэтому, если вы хотите сохранить articlestat без article, возникнет ошибка.

0 голосов
/ 19 февраля 2019
Поле

article в вашей модели ArticleStat не является обязательным.Вы не можете сохранить свой объект ArticleStat без ForeignKey для Article

Вот аналогичный код, item - это ForeignKey для модели Item, и он необходим.

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

Если я пытаюсь сохранить объект Interaction из оболочки, не выбирая ForeignKey для элемента, я получаю IntegrityError.

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

Вам не нужна проверка self.article.exists().Django и Database потребуют это поле и не позволят вам сохранить объект без него.

Вы должны прочитать о поле ForeignKey в Django Docs

0 голосов
/ 19 февраля 2019

Если вы используете реляционную базу данных, ограничения внешнего ключа будут добавлены автоматически после миграции.Метод save может не нуждаться в настройке.

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

Используйте следующий код для создания ArticleStats

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

Если аргумент article_id действителен, объекты ArticleStats создаются, иначе возникает IntegrityError.

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

Примечание: протестировано на MySQL v5.7, Django 1.11

0 голосов
/ 18 февраля 2019

Если вы хотите убедиться, что Article существует в методе ArticleStat * save, вы можете попробовать получить его из своей базы данных, а не просто проверить self.article.

Цитата Алекс Мартелли :

"... Знаменитый девиз Грейс Мюррей Хоппер:" Проще просить прощения, чем разрешения ", имеет множество полезных приложений- в Python, ... "

Я думаю, использование try .. except .. else более питонично, и я сделаю что-то вроде этого:

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)
0 голосов
/ 14 февраля 2019

Вы можете просто проверить значение поля article.Если он не установлен, я полагаю, что по умолчанию он равен None.

if self.article:  # Value is set

Если вы хотите, чтобы это поле ForeignKey было необязательным (что звучит так, как вы), вам нужно установить blank=True и null=True на этом поле.Это позволит оставить поле пустым (при проверке) и установит null в поле, когда его там нет.

Как уже упоминалось в комментариях ниже, ваша база данных, скорее всего, обеспечивает факт того, что полетребуется и отказывается удалять экземпляр article.

...