Django: Filtering via SQL, not Python - python

I created the following context variables context["genders"] and context["ages"].
Currently, there is a lot of work done by Python under #Filtering, while I think it would be better done in #Query.
However, that's where I currently struggle. Do you have an idea on how to achieve the pre-filtering in the #Query section via SQL?
Please not the int(answer_obj.answer) as answer is a TextField.
# Query
responses = Response.objects.filter(
survey__event=12, survey__template=settings.SURVEY_POST_EVENT
).order_by("-created")
# Filtering
filtered_responses = []
for response in responses:
for answer_obj in response.answers.all():
if (
answer_obj.question.focus == QuestionFocus.RECOMMENDATION_TO_FRIENDS
and int(answer_obj.answer) >= 8
):
filtered_responses.append(response)
# Context
gender_list = []
age_list = []
for response in filtered_responses:
for answer_obj in response.answers.all():
# Here a list of all the genders that gave that answer:
if answer_obj.question.focus == QuestionFocus.GENDER:
gender_list.append(answer_obj.answer)
# Here a list of all the ages that gave that answer:
if answer_obj.question.focus == QuestionFocus.AGE:
age_list.append(answer_obj.answer)
context["genders"] = gender_list
context["ages"] = age_list
models.py
class Answer(TimeStampedModel):
question = models.ForeignKey(
"surveys.Question", on_delete=models.CASCADE, related_name="answers"
)
response = models.ForeignKey(
"Response", on_delete=models.CASCADE, related_name="answers"
)
answer = models.TextField(verbose_name=_("Answer"))
choices = models.ManyToManyField(
"surveys.AnswerOption", related_name="answers", blank=True
)
class Response(TimeStampedModel):
class Language(Choices):
CHOICES = settings.LANGUAGES
survey = models.ForeignKey(
"surveys.Survey", on_delete=models.CASCADE, related_name="responses"
)
order = models.ForeignKey(
"orders.Order",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="response",
)
attendee = models.ForeignKey(
"attendees.Attendee",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="response",
)
total_time = models.PositiveIntegerField(
null=True, blank=True, verbose_name=_("Total time")
)
ip_address = models.GenericIPAddressField(null=True, verbose_name=_("IP Address"))
language = models.CharField(
max_length=Language.get_max_length(),
choices=Language.CHOICES,
verbose_name=_("Language"),
)
class Question(TimeStampedModel):
survey = models.ForeignKey(
"surveys.Survey", on_delete=models.CASCADE, related_name="questions"
)
question_set = models.ForeignKey(
"QuestionSet", on_delete=models.CASCADE, related_name="questions"
)
title = models.CharField(max_length=100, verbose_name=_("Title"))
help_text = models.TextField(null=True, blank=True, verbose_name=_("Help text"))
type = models.CharField(
max_length=QuestionType.get_max_length(),
choices=QuestionType.CHOICES,
verbose_name=_("Question type"),
)
focus = models.CharField(
max_length=QuestionFocus.get_max_length(),
choices=QuestionFocus.CHOICES,
verbose_name=_("Question focus"),
)
required = models.BooleanField(default=False, verbose_name=_("Is required?"))
position = models.PositiveSmallIntegerField(
null=True, blank=True, verbose_name=_("Position")
)
# Translatable fields
i18n = TranslationField(fields=("title", "help_text"))
class Meta:
ordering = ("position", "pk")

Filtering
It looks to me, that you want all responses, where answer is higher than eight for question focused on friend recommendation. Is it expected that you might have the same response appended to the filtered responses more than once, or will there be only one question of this type? I think you could rewrite it as follows:
filtered_response = responses.filter(
answers__question__focus=QuestionFocus.RECOMMENDATION_TO_FRIENDS
).annotate(
answer_num=Cast("answers__answer", IntegerField()),
).filter(
answer_num__gt=8,
)
And populating the context:
context["genders"] = Answer.objects.filter(
response_id__in=filtered_response.values_list("id", flat=True),
question__focus=QuestionFocus.GENDER,
).values_list("answer", flat=True)
context["ages"] = Answer.objects.filter(
response_id__in=filtered_response.values_list("id", flat=True),
question__focus=QuestionFocus.AGE,
).values_list("answer", flat=True)
This should allow you to avoid firing the queries to iterate over response.answers.all(), and hopefully achieve the same result.

Related

Query unique values inside django forloop

I have a query where I should avoid double entry of the same question. In fact, I would like to get only unique values but, I am using the distinct() django function which isn't working.
I have these models:
class QuestionTopic(models.Model):
name = models.CharField(max_length=255)
question_subject = models.ForeignKey(
QuestionSubject, on_delete=models.CASCADE)
exam_questions_num = models.IntegerField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
class Meta:
ordering = ('created_at',)
class Question(models.Model):
id = models.CharField(max_length=7,
unique=True,
primary_key=True,
editable=False)
question_subject = models.ForeignKey(
QuestionSubject, on_delete=models.CASCADE)
text = tinymce_models.HTMLField()
mark = models.IntegerField(default=1)
is_published = models.BooleanField(default=True)
question_bank_id = models.CharField(max_length=255, blank=True, null=True)
question_topic = models.ForeignKey(
QuestionTopic, on_delete=models.CASCADE, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
and the query is:
subject = QuestionSubject.objects.get(id=request.POST.get('subject'))
question_topics = QuestionTopic.objects.filter(
question_subject=subject)
questions_list = []
for topic in question_topics:
for q in range(topic.exam_questions_num):
question = Question.objects.filter(
question_subject=subject, question_topic=topic).values_list(
'id', flat=True).order_by('?').distinct().first()
questions_list.append(question)
What I would like to achieve is to have all different questions for each different topic inside questions_list. Which I am not achieving at the moment being the distinct used inside a loop.
You can work with a single query with:
subject = QuestionSubject.objects.get(id=request.POST.get('subject'))
question_topics = QuestionTopic.objects.filter(question_subject=subject)
questions_list = [
question
for topic in question_topics
for question in Question.objects.filter(
question_subject=subject, question_topic=topic
).order_by('?')[: topic.exam_questions_num]
]
This will make O(n) queries with n the number of topics.
If the number of questions is not that large, you can do this with two queries with:
from random import sample
subject = QuestionSubject.objects.get(id=request.POST.get('subject'))
question_topics = QuestionTopic.objects.filter(
question_subject=subject
).prefetch_related('question_set')
questions_list = [
question
for topic in question_topics
for question in sample(topic.question_set.all(), topic.exam_question_num)
]
Just make questions_list a set:
questions_list = set()
Sets don't allow a value more than once.

QuerySet: Filter for entries

I want to show all answers for question__focus=QuestionFocus.REASON_FOR_ATTENDING where question__focus=QuestionFocus.RECOMMENDATION_TO_FRIENDS is >= 9.
The answer field is not an integer field, but a TextField as all questions share the same Answer model. I tried a lot, but nothing worked for me so far.
I first tried to go from the Answer model, but that also didn't work as I filter for different answers than what I want to show at the end.
event = Event.objects.get(pk=12)
survey = event.surveys.get(
template=settings.SURVEY_POST_EVENT
).questions.[HOW TO CONTINUE?]
models.py
class Survey(TimeStampedModel):
class SurveyTemplate(Choices):
CHOICES = ((survey, survey) for survey in settings.SURVEY_TEMPLATES.keys())
id = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4)
event = models.ForeignKey(
"events.Event", on_delete=models.CASCADE, related_name="surveys"
)
is_active = models.BooleanField(default=False, verbose_name=_("Is active?"))
template = models.CharField(
max_length=SurveyTemplate.get_max_length(),
choices=SurveyTemplate.CHOICES,
verbose_name=_("Survey template"),
)
class Response(TimeStampedModel):
class Language(Choices):
CHOICES = settings.LANGUAGES
survey = models.ForeignKey(
"surveys.Survey", on_delete=models.CASCADE, related_name="responses"
)
order = models.ForeignKey(
"orders.Order",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="response",
)
attendee = models.ForeignKey(
"attendees.Attendee",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="response",
)
total_time = models.PositiveIntegerField(
null=True, blank=True, verbose_name=_("Total time")
)
ip_address = models.GenericIPAddressField(null=True, verbose_name=_("IP Address"))
language = models.CharField(
max_length=Language.get_max_length(),
choices=Language.CHOICES,
verbose_name=_("Language"),
)
class Answer(TimeStampedModel):
question = models.ForeignKey(
"surveys.Question", on_delete=models.CASCADE, related_name="answers"
)
response = models.ForeignKey(
"Response", on_delete=models.CASCADE, related_name="answers"
)
answer = models.TextField(verbose_name=_("Answer"))
choices = models.ManyToManyField(
"surveys.AnswerOption", related_name="answers", blank=True
)
class Question(TimeStampedModel):
survey = models.ForeignKey(
"surveys.Survey", on_delete=models.CASCADE, related_name="questions"
)
question_set = models.ForeignKey(
"QuestionSet", on_delete=models.CASCADE, related_name="questions"
)
title = models.CharField(max_length=100, verbose_name=_("Title"))
help_text = models.TextField(null=True, blank=True, verbose_name=_("Help text"))
type = models.CharField(
max_length=QuestionType.get_max_length(),
choices=QuestionType.CHOICES,
verbose_name=_("Question type"),
)
focus = models.CharField(
max_length=QuestionFocus.get_max_length(),
choices=QuestionFocus.CHOICES,
verbose_name=_("Question focus"),
)
required = models.BooleanField(default=False, verbose_name=_("Is required?"))
position = models.PositiveSmallIntegerField(
null=True, blank=True, verbose_name=_("Position")
)

Django: Suggestion for models design

I need help with creating models for my simple Django app.
The purpose of the application is to let users (referees) register for matches, then admin will choose 2 users (referees) from the list of registered for given match. Right now my Matches model looks like below:
class Match(models.Model):
match_number = models.CharField(
max_length=10
)
home_team = models.ForeignKey(
Team,
on_delete=models.SET_NULL,
null=True,
related_name='home_team'
)
away_team = models.ForeignKey(
Team,
on_delete=models.SET_NULL,
null=True,
related_name='away_team'
)
match_category = models.ForeignKey(
MatchCategory,
on_delete=models.SET_NULL,
null=True
)
date_time = models.DateTimeField(
default=timezone.now
)
notes = models.TextField(
max_length=1000,
blank=True
)
What I thought to do is to create new Model named MatchRegister where I will be saving match_id and user_id, something like below:
class MatchRegister(models.Model):
match_id = models.ForeignKey(
Match
)
user_id = models.ForeignKey(
Users
)
And than admin will have list of registered user for given match from which he will choose two, so I thought to modify my Match model like this (add two new Fields):
class Match(models.Model):
match_number = models.CharField(
max_length=10
)
home_team = models.ForeignKey(
Team,
on_delete=models.SET_NULL,
null=True,
related_name='home_team'
)
away_team = models.ForeignKey(
Team,
on_delete=models.SET_NULL,
null=True,
related_name='away_team'
)
match_category = models.ForeignKey(
MatchCategory,
on_delete=models.SET_NULL,
null=True
)
date_time = models.DateTimeField(
default=timezone.now
)
notes = models.TextField(
max_length=1000,
blank=True
)
ref_a = models.ForeignKey(
Users,
on_delete=models.SET_NULL,
null=True,
related_name='ref_a'
)
ref_b = models.ForeignKey(
Users,
on_delete=models.SET_NULL,
null=True,
related_name='ref_b'
)
This is my solution but I don't know if it is done in proper way so I want to ask you for help.
If you know for certain that matches will only ever have two refs, then what you propose is just fine. However, if there's an opportunity in the future for the number to change (only one, or perhaps three), an alternative would be to add a flag to the intermediate table:
class MatchRegister(models.Model):
match_id = models.ForeignKey(Match)
user_id = models.ForeignKey(Users)
chosen = models.BooleanField(default=False)
You would need business logic to constrain the number of "chosen" refs to the number you anticipate. This option makes it easy to increase or decrease the number of refs without adding or removing columns (just change the business logic).

Django query get objects that are not in other object

I have two Django models: Match and MatchRegister. I want to get list of all Matches that are not in MatchRegister object but I'm unable to do so. Could you please help me achieve it?
Below my two classes
class Match(models.Model):
"""Model representing Match object"""
match_number = models.CharField(
max_length=10
)
home_team = models.ForeignKey(
Team,
on_delete=models.SET_NULL,
null=True,
related_name='home_team'
)
away_team = models.ForeignKey(
Team,
on_delete=models.SET_NULL,
null=True,
related_name='away_team'
)
match_category = models.ForeignKey(
MatchCategory,
on_delete=models.SET_NULL,
null=True
)
date_time = models.DateTimeField(
default=timezone.now
)
notes = models.TextField(
max_length=1000,
blank=True
)
last_update = models.DateTimeField(
auto_now=timezone.now
)
class MatchRegister(models.Model):
match = models.ForeignKey(
Match,
on_delete=models.SET_NULL,
null=True
)
You can use the __isnull filter with the correct related_query_name (which defaults to the lower case model name):
Match.objects.filter(matchregister__isnull=True)
You could take list of all the matches connected with a match register and then exclude it from the matches altogether, like this:
all_match_registers = MatchRegister.objects.all()
ids_to_exclude = []
for match_register in all_match_registers:
ids_to_exclude.append(match_register.match.id)
Match.objects.exclude(id__in = ids_to_exclude)

Django drag-n-drop form validation

DJANGO 2.0
So basically I was tasked to make a drag and drop form, in which I would have a table of available users, and create a team from that table, by dragging and dropping the users.
My problem is, that I have no idea where to start tackling this problem, mostly because I'm not sure how to validate the data that is dropped after I hit submit.
So my goals are:
Validate drag-n-drop data
Add a user status so it won't be reassigned to another team.
Assign credits to users upon competition of the given task (this is used for making reports and to add an "achievements" functionalities)
Anything leading to drag-n-drop validation in django will be helpful too
My models.py for users
class Employee(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
department = models.CharField(
max_length=100,
default=''
)
title = models.CharField(
max_length=60,
default=''
)
date_joined = models.DateField(
default=datetime.date.today
)
avatar = models.ImageField(
upload_to="avatars"
)
My models.py for creating the teams, (I still need to write a Foreign Key or something to store the users that will be on the team):
class Task(models.Model):
Task_name = models.CharField(
max_length=15,
default=""
)
Task_date = models.DateField()
Task_deadline = models.DateField()
Task_credits = models.DecimalField(
max_digits=2,
decimal_places=0
)
Task_reference = models.CharField(
max_length=100,
default=''
)
Task_description = models.TextField(
max_length=3000
)
Task_notes = models.TextField(
max_length=500
)
Task_completition = models.CharField(
max_length=15,
default=_lazy('Assigned')
choices=
)
Task_tags = models.CharField(
max_length=20,
default='',
)

Categories

Resources