Django Модель наследования для эффективного кода - PullRequest
0 голосов
/ 01 мая 2018

У меня есть приложение Django, которое использует абстрактный базовый класс («Ответ») и создает разные ответы в зависимости от типа ответа, требуемого объектами Вопрос. (Этот проект начал свою жизнь как учебник Опросы). Вопрос сейчас:

class Question(models.Model):
    ANSWER_TYPE_CHOICES = (
    ('CH', 'Choice'),
    ('SA', 'Short Answer'),
    ('LA', 'Long Answer'),
    ('E3', 'Expert Judgement of Probabilities'),
    ('E4', 'Expert Judgment of Values'),
    ('BS', 'Brainstorms'),
    ('FB', 'Feedback'),
    )
    answer_type = models.CharField(max_length=2,
                               choices=ANSWER_TYPE_CHOICES,
                               default='SA')
    question_text = models.CharField(max_length=200, default="enter a question here")

И ответ:

class Answer(models.Model):
"""
Answer is an abstract base class which ensures that question and user are
always defined for every answer
"""
question = models.ForeignKey(Question, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
class Meta:
    abstract = True
    ordering = ['user']

На данный момент у меня есть один метод в Answer (перезаписывающий get_or_update_answer ()) с инструкциями для конкретного типа, чтобы посмотреть в нужной таблице и собрать или создать нужный тип объекта.

    @classmethod
def get_or_update_answer(self, user, question, submitted_value={}, pk_ans=None):
    """
    this replaces get_or_update_answer with appropriate handling for all
    different Answer types. This allows the views answer and page_view to get
    or create answer objects for every question type calling this function.
    """
    if question.answer_type == 'CH':
        if not submitted_value:
            # by default, select the top of a set of radio buttons
            selected_choice = question.choice_set.first()
            answer, _created = Vote.objects.get_or_create(
                user=user,
                question=question,
                defaults={'choice': selected_choice})
        else:
            selected_choice = question.choice_set.get(pk=submitted_value)
            answer = Vote.objects.get(user=user, question=question)
            answer.choice = selected_choice

    elif question.answer_type == 'SA':
        if not submitted_value:
            submitted_value = ""
            answer, _created = Short_Answer.objects.get_or_create(
                user=user,
                question=question,
                defaults={'short_answer': submitted_value})
        else:
            answer = Short_Answer.objects.get(
                user=user,
                question=question)
            answer.short_answer = hashtag_cleaner(submitted_value['short_answer'])
 etc... etc... (similar handling for five more types)

Поместив всю эту логику в «models.py», я могу загрузить пользовательские ответы для page_view для любого количества вопросов с помощью:

    for question in page_question_list:
        answers[question] = Answer.get_or_update_answer(user, question, submitted_value, pk_ans)

Я считаю, что есть более Pythonic способ разработки этого кода - то, что я не научился использовать, но я не уверен, что. Что-то вроде интерфейсов, так что каждый тип объекта может реализовать свою собственную версию Answer.get_or_update_answer (), а Python будет использовать версию, подходящую для объекта. Это сделало бы расширение 'models.py' намного лучше.

Ответы [ 2 ]

0 голосов
/ 25 июня 2018

Я недавно вернулся к этой проблеме, заменил одну или двести строк кода на пять или десять и подумал, что однажды кому-нибудь будет полезно найти то, что я здесь сделал.

Есть несколько элементов проблемы, с которой я столкнулся: во-первых, многие типы ответов должны быть созданы, сохранены и извлечены при необходимости; во-вторых, дихотомия GET vs POST (и мое уникальное решение всегда создавать ответ, отправляя его в форму); в-третьих, некоторые типы имеют разную логику (мозговой штурм может иметь несколько ответов на пользователя, обратная связь даже не нуждается в ответе - если он создан для пользователя, он был представлен.) Эти элементы, вероятно, скрывают некоторую возможность удаления повторение, которое делает шаблон посетителя вполне подходящим.

Решение для элементов 1 и 2

Словарь кодов question.answer_type, который сопоставляется с соответствующим подклассом ответа, создается в views.py (поскольку его трудно разместить в models.py и разрешить зависимости):

# views.py: 
ANSWER_CLASS_DICT = {
'CH': Vote,
'SA': Short_Answer,
'LA': Long_Answer,
'E3': EJ_three_field,
'E4': EJ_four_field,
'BS': Brainstorm,
'FB': FB,}

Тогда я могу получить класс ответа, который я хочу получить get_or_created для любого вопроса:

ANSWER_CLASS_DICT[question.answer_type]

Я передаю его в качестве параметра методу класса:

# models.py:
def get_or_update_answer(self, user, question, Cls, submitted_value=None, pk_ans=None):            
    if not submitted_value:
            answer, _created = Cls.objects.get_or_create(user=user, question=question)
    elif isinstance(submitted_value, dict):
            answer, _created = Cls.objects.get_or_create(user=user, question=question)
        for key, value in submitted_value.items():
                setattr(answer, key, value)
    else:
        pass

Таким образом, те же шесть строк кода обрабатывают get_or_creating любой Ответ, когда submit_value = Нет (GET) или нет (submit_value).

Решение для элемента 3

Решением для третьего элемента было расширение модели, чтобы разделить как минимум три типа обработки для пользователей, повторно посещающих один и тот же вопрос: «S» - одиночный, который позволяет им записывать только один ответ, пересматривать и исправлять ответ, но никогда не давать два разных ответа. 'T' - отслеживается, что позволяет им каждый раз обновлять свой ответ, но делает историю того, что их ответ был доступен (например, для исследователей). 'M' - множественное число, позволяющее ответить на один вопрос.

После всех этих изменений все еще исправляются ошибки, поэтому я не буду публиковать код. Следующая функция: составные вопросы и шаблоны вопросов, чтобы люди могли использовать администратора для вывода на экран, чтобы сделать свои собственные типы ответов.

0 голосов
/ 01 мая 2018

Исходя из того, что вы показали, вы в большинстве случаев реализуете шаблон Visitor , который является довольно стандартным способом обработки подобных ситуаций (у вас есть куча связанных подклассов). каждый из которых нуждается в собственной логике обработки и хочет перебирать их экземпляры и что-то делать с каждым).

Я бы посоветовал взглянуть на то, как работает этот шаблон, и, возможно, реализовать его более явно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...