Django 3.2 как зациклить поле с иностранными ключами

Я хочу зациклить поле, основанное на модели вопроса. Вот мои модели.## Заголовок ##

models.py

from django.db import models
from django.db.models.deletion import CASCADE
from django.conf import settings
class Question(models.Model):
    id = models.BigAutoField(primary_key=True)
    title = models.CharField(max_length=50, unique=True)
    question = models.CharField(max_length=255, unique=True)
class Declaration(models.Model):
    id = models.BigAutoField(primary_key=True)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='declarations', on_delete=models.CASCADE)
class Checklist(models.Model):
    id = models.BigAutoField(primary_key=True)
    declaration = models.ForeignKey(Declaration, related_name='checklist_declaration', on_delete=models.CASCADE)
    question = models.ForeignKey(Question, related_name='checklist_question', on_delete=models.CASCADE, limit_choices_to={'is_active': 'True'})
    is_yes = models.BooleanField()

и мой forms.py

from django.utils.safestring import mark_safe
from django import forms
from declaration.models import Checklist, Question

class HorizontalRadionRenderer(forms.RadioSelect):
    def render(self):
        return mark_safe(u'\n'.join([u'%s\n' % w for w in self]))

class ChecklistForm(forms.ModelForm):
    is_yes = forms.TypedChoiceField(
        coerce=lambda x: x == True,
        choices=((True, 'Yes'), (False, 'No')),
        widget=forms.RadioSelect()
    )

    class Meta:
        model = Checklist
        fields = ['is_yes']

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        questions = Question.objects.filter(group='symptom')
        for q in questions:
            self.fields['question_id'] = q.id

и вот мой ожидаемый результат введите здесь описание изображения

Я не знаю, как сделать это в forms.py, поскольку единственный способ валидации полей в django - через формы.

Вам понадобятся две формы, DeclarationForm и ChecklistForm (хотя я бы, вероятно, переименовал вашу модель Checklist в DeclarationQuestionAnswer). Удалите код __init__ из вашей формы ChecklistForm и добавьте все поля в fields.

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

questions = []
for q in Question.objects.all():
    questions.append({'question': str(q.id), 'is_yes': None})
formset = inlineformset_factory(Declaration, Checklist,
    form=ChecklistForm, fk_name="declaration", extra=len(questions),
                can_delete=False)
question_formset = formset(prefix='questions_fs', initial=questions)

Привет, Тензай, и добро пожаловать в великое сообщество stackoverflow!

1. django model.ManyToManyField

1.

Одним из способов справиться с такой архитектурой "многие ко многим" (Checklist) является использование виджета администратора django с объявлением models.ManyToManyField. Виджет в forms.py from django.contrib.admin.widgets import FilteredSelectMultiple. Но для этого вам придется упростить вашу модель.

  1. Checklist.is_yes поле бесполезно, так как наличие данных в таблице контрольного списка свидетельствует о том, что пользователь отметил вопрос.
  2. Если у вас нет другого поля в классе Declaration, то оно также бесполезно.

Отсюда единственное, что вам нужно, это объявление отношения many2many к классу Question из класса User. Но в этом случае вам придется использовать custom user model или one to one relation to Auth.models.User class of django. Cfr. Как расширить пользовательскую модель Django

# Add this question field to the custom User class
question = models.ManyToManyField(Question, verbose_name="Question")

Форма стала

from django.contrib.auth import get_user_model
from django.contrib.admin.widgets import FilteredSelectMultiple

class ChecklistForm(forms.ModelForm):

    class Meta:
        model = get_user_model()
        widgets = {"question" : 
             FilteredSelectMultiple(Question._meta.verbose_name_plural, False)
        }

Но это приведет к простому перетаскиванию ваших вопросов слева направо.

2. Left join raw SQL

2.

Другой способ сделать это - построить представление функции с левым соединением необработанного SQL. Сохраните свою модель как есть

2.1. models.py

Заметки из пункта 1. остается и поэтому Checklist класс становится

from django.contrib.auth import get_user_model

my_user_model = get_user_model()

class Checklist(models.Model):
    id = models.BigAutoField(primary_key=True)
    user = models.ForeignKey(my_user_model, related_name='checklist_declaration', on_delete=models.CASCADE)
    question = models.ForeignKey(Question, related_name='checklist_question', on_delete=models.CASCADE)

Вы должны использовать эту архитектуру, если планируете добавить определенные поля в контрольный список (комбинации вопросов и пользователей)

2.2. views.py

Примечание для метода Checklist.is_deletable, вызываемого в представлении:

Если выбранные поля вопросов ссылаются где-то в вашей БД, вам нужно убедиться, что отмена выбора не будет удалить все связанные поля указанного пользовательского вопроса в вашей БД. Для этого необходимо is_deletable вмешивается метод

.
import logging

from django.shortcuts import render, redirect
from django.utils.translation import gettext_lazy as _
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from .models import Checklist, Question

LOG = logging.getLogger(__name__)

@login_required
def my_question(request):
    """
    Function view based on raw select in order to manage outer join
    and display
    """
    myuser = request.user
    qchecks = Checklist.objects.raw('''
    select qq.id, qq.title, aa.qid from question_question as qq
    left join (select question_id as qid
                 from question_checklist
                where user_id = {0}) aa
    on aa.qid = qq.id
    order by qq.title ASC
    '''.format(myuser.id))

    if request.method == 'POST':
        # selected elements provided by the form
        selected = request.POST.getlist("qcheck")
        for ticked in selected:
            ticked_inst = Question.objects.get(id=ticked)
            Checklist.objects.for_user(myuser).get_or_create(profile=myuser,
                                                             question=ticked_inst)

        # Get unselected elements and remove them
        unselected_inst = Question.objects.exclude(id__in=selected)
        for unticked in unselected_inst:
            try:
                obj = Checklist.objects.filter(user=myuser).get(question=unticked)
                # check related before delete
                related_list = Checklist.is_deletable(obj)
                if related_list:
                    messages.error(request,
                        _(f"Checklist delete {obj}. Please first delete related records.")
                    )
                    for related in related_list:
                        related_model = related.model._meta.verbose_name
                        related_values = [i.__str__() for i in related.all()]
                        msg = f"{related_model}: {related_values}"
                        messages.warning(request, msg)
                    return redirect('my_question')
                else:
                    obj.delete()
            except Checklist.DoesNotExist:
                pass
        messages.success(request, _('Changes saved'),
                                fail_silently=True)
        
        return redirect('my_question')

    context = {'user': request.user,
               'qchecks': qchecks,
               'myuser': myuser,
               'upd_allowed': upd_allowed}

    return render(request, 'select_question_form.html', context)

2.3. html-шаблон (select_question_form.html)

{% extends 'base.html' %}
{% load i18n %}

{% block content %}

<h2>Do you have</h2>

{% if qchecks %}
    <table class="table">
        <form action="" method="post" enctype="multipart/form-data">
        {% csrf_token %}
        {% for qcheck in qchecks %}
            <tr>
                <td>{{ qcheck.title }}</td>
                <td>
                    <input type="checkbox" name="qcheck" value="{{ qcheck.id }}" {% if qcheck.id == qcheck.qid %} checked {% endif %}
                    {% if not upd_allowed%} disabled{% endif %}/>
                </td>
            </tr>
        {% endfor %}
        {% if upd_allowed %}
        <tr><td colspan="2"><input class="btn btn-danger2" type="submit" value="{% trans "Save" %}" /></td></tr>
        {% endif %}
        
        </form>
    </table>
{% else %}
    <p>No question check.</p>
{% endif %}

{% endblock content %}

2.4 urls.py

from django.urls import path
from . import views

urlpatterns = [
    path('questions', views.my_question, name='my_question')
]
Вернуться на верх